【动态规划---dp经典问题总结:】

发布时间:2024年01月19日

动态规划---dp经典问题总结:

1.01背包

有?N?件物品和一个容量是?V?的背包。每件物品只能使用一次。

第?i?件物品的体积是?vi,价值是?wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式

第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有?N?行,每行两个整数?vi,wi,用空格隔开,分别表示第?i?件物品的体积和价值。

输出格式

输出一个整数,表示最大价值。

数据范围

0<N,V≤10000≤1000
0<vi,wi≤10000≤1000

输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8

?首先,状态表示:1.集合:所有只考虑前i个物品,并且总体积不超过j的选法集合。2.属性;max。

然后,状态计算:将f(i,j)分为所有不选择第i个物品的方案和所有选第i个物品的方案,第一个方案方程为f(i-1,j):表示从前i-1个物品中选,第二个方案方程为f(i-1,j-v[i])+w[i],然后就变成了f(i,j)=max(f(i-1,j),f(i-1,j-v[i])+w[i]),最后输出f[n][m]。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N][N];
int n,m,v[N],w[N];
int main()
{
? ? cin>>n>>m;
? ? for(int i=1;i<=N;++i)
? ? cin >> v[i] >> w[i];
? ? for(int i=1;i<=n;++i)
? ? for(int j=1;j<=m;++j){
? ? ? ? f[i][j]=f[i-1][j];
? ? ? ? if(j>=v[i]) f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
? ? }
? ? cout << f[n][m];
? ? return 0;
}

?2.完全背包

有?N种物品和一个容量是?V?的背包,每种物品都有无限件可用。

第?i种物品的体积是?vi,价值是?wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式

第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有?N 行,每行两个整数?vi,wi,用空格隔开,分别表示第?i?种物品的体积和价值。

输出格式

输出一个整数,表示最大价值。

数据范围

0<N,V≤10000≤1000
0<vi,wi≤10000≤1000

输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10

?首先,状态表示:1.集合:和01背包一样。2.属性:max。然后,状态计算:因为是无限次用物品,所以f(i,j)可划分为多个子集合:选0个第i个物品,选1个,选2个........选k个.......? ? 分析选0个,方程为f(i-1,j),以选k个为例,方程为f(i-1,j-k*v[i])+w[i],然后联立方程f(i,j)=max(f(i-1,j),f(i-1,j-v)+w,f(i-1,2v)+w,.......)和f(i,j-v)=max(f(i-1,j-v),f(i-1,j-2v)+w,f(i-1,j-3v)+w,........),得到f(i,j)=max(f(i-1,j),f(i,j-v)+w)。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int v[N],w[N];
int n,m;
int f[N][N];
int main()
{
? ? cin >> n >> m;
? ? for (int i = 1; i <= n; i ++ )
? ? cin >> v[i] >> w[i];
? ? for (int i = 1; i <= n; i ++ )
? ? for (int j = 1; j <= m; j ++ ){
? ? ? ? f[i][j]=f[i-1][j];
? ? ? ? if(j>=v[i]){
? ? ? ? ? ? f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i]);
? ? ? ? }
? ? }
? ? cout << f[n][m];
? ? return 0;
}

3.石子合并(经典区间dp)

设有?N�?堆石子排成一排,其编号为?1,2,3,…,N1,2,3,…,�。

每堆石子有一定的质量,可以用一个整数来描述,现在要将这?N�?堆石子合并成为一堆。

每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。

例如有?44?堆石子分别为?1 3 5 2, 我们可以先合并?1、21、2?堆,代价为?44,得到?4 5 2, 又合并?1、21、2?堆,代价为?99,得到?9 2?,再合并得到?1111,总代价为?4+9+11=244+9+11=24;

如果第二步是先合并?2、32、3?堆,则代价为?77,得到?4 7,最后一次合并代价为?1111,总代价为?4+7+11=224+7+11=22。

问题是:找出一种合理的方法,使总的代价最小,输出最小代价。

输入格式

第一行一个数?N�?表示石子的堆数?N�。

第二行?N�?个数,表示每堆石子的质量(均不超过?10001000)。

输出格式

输出一个整数,表示最小代价。

数据范围

1≤N≤3001≤�≤300

输入样例:
4
1 3 5 2
输出样例:
22

集合:所有将[i,j]合并成一堆方案的集合。属性:min。

状态计算:?将f(i,j)区间分为从i到k,k+1到j,然后合并两边(前缀和思想)s[j]-s[i-1],所以最后f(i,j)=min(f(i,j),f(i,k)+f(k+1,j)+s[j]-s[i-1])。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 310;
int n,INF=1e9;
int f[N][N];
int w[N],s[N];
int main()
{
? ? cin >> n;
? ? for (int i = 1; i <= n; i ++ ) cin >> w[i];
? ? for (int i = 1; i <= n; i ++ ) s[i]=s[i-1]+w[i];//前缀和
? ? for(int len=2;len<=n;++len)
? ? for(int i=1;i+len-1<=n;++i){
? ? ?int j=i+len-1;
? ? ?f[i][j]=INF;
? ? ?for(int k=i;k<j;++k){
? ? ? ? ?f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]);
? ? ?}
? ? }
? ? cout << f[1][n];
? ? return 0;
}

4.最长公共子序列

给出 1,2,…… ,n 的两个排列 A 和 B ,求它们的最长公共子序列。

输入格式

第一行是一个数 n。

接下来两行,每行为 n 个数,为自然数 1,2,…… ,n 的一个排列。

输出格式

一个数,即最长公共子序列的长度。

样例输入?
5?
3 2 1 4 5
1 2 3 4 5

样例输出?
3

提示

- 对于 50% 的数据, n <= 10^3;
- 对于 100% 的数据, n <= 10^5。

集合:所有A[1--i]与B[1--j]的公共子序列集合。属性:max。

状态计算:分为4种(其实是3种),不含a[i],b[j],含a[i],不含b[j],不含a[i],含b[j],都包含。然后第一种可以分到第二种或第三种里面,所以是三种,首先先在中间两种中取max,第四种是需要判断是否a[i]==b[j]。

#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m;
char a[N],b[N];
int f[N][N];
int main()
{
cin>>n>>m>>a+1>>b+1;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j){
?? ?f[i][j]=max(f[i-1][j],f[i][j-1]);
?? ?if(a[i]==b[j]) f[i][j]=max(f[i][j],f[i-1][j-1]+1);
}
cout<<f[n][m];
?? ?return 0;
}

5.采药

辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。

为此,他想拜附近最有威望的医师为师。

医师为了判断他的资质,给他出了一个难题。

医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”

如果你是辰辰,你能完成这个任务吗?

输入格式

输入文件的第一行有两个整数?T�?和?M�,用一个空格隔开,T�?代表总共能够用来采药的时间,M�?代表山洞里的草药的数目。

接下来的?M�?行每行包括两个在?11?到?100100?之间(包括?11?和?100100)的整数,分别表示采摘某株草药的时间和这株草药的价值。

输出格式

输出文件包括一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。

数据范围

1≤T≤10001≤�≤1000,
1≤M≤1001≤�≤100

输入样例:
70 3
71 100
69 1
1 2
输出样例:
3

这道题可以看作01背包,就是将时间看作背包容量(体积),每一株草药看作物品?,所以集合就是:只看前i种草药,且总体积不超过j的最大价值的选法。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int n,m;
int f[N][N];
int v[N],w[N];
int main()
{
? ? cin >> m>>n;//时间和数目(体积和个数)
? ? for (int i = 1; i <= n; i ++ ) cin >>v[i]>>w[i];//时间和价值(体积和价值)
? ? for (int i = 1; i <= n; i ++ )//前i个物品
? ? for (int j = 1; j <= m; j ++ ){//总体积不超过j
? ? ? ? f[i][j]=f[i-1][j];
? ? ? ? if(j>=v[i]) f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
? ? }
? ? cout << f[n][m];
? ? return 0;
}

6.数字三角形

给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。

        7
      3   8
    8   1   0
  2   7   4   4
4   5   2   6   5
输入格式

第一行包含整数?n�,表示数字三角形的层数。

接下来?n�?行,每行包含若干整数,其中第?i�?行表示数字三角形第?i�?层包含的整数。

输出格式

输出一个整数,表示最大的路径数字和。

数据范围

1≤n≤5001≤�≤500,
?10000≤三角形中的整数≤10000?10000≤三角形中的整数≤10000

输入样例:
5
7
3 8
8 1 0 
2 7 4 4
4 5 2 6 5
输出样例:
30

经典的线性dp问题,核心是从下到上考虑,所以集合为:从底到上走到f(i,j)的所有路线集合。属性?:max。f(i,j)有两个分支,分别为从f(i+1,j)走到f(i,j)【不管前面怎么走,最后一步是从f(i+1,j)走到f(i,j)】,和f(i+1,j+1)走到f(i,j)【同理】,最后结果为f(1,1)。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 10010;
int n;
int f[N][N];
int main()
{
? ? cin >> n;
? ? for (int i=1;i<=n;++i)
? ? for(int j=1;j<=i;++j){
? ? ? ? scanf("%d",&f[i][j]);
? ? }
? ? for(int i=n-1;i>=1;--i)
? ? for(int j=1;j<=i;++j){
? ? ? ? f[i][j]+=max(f[i+1][j],f[i+1][j+1]);
? ? }
? ? cout << f[1][1];
? ? return 0;
}

文章来源:https://blog.csdn.net/2301_80338843/article/details/135575758
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。