当初学者最开始学习 dp 的时候往往接触的是一大堆的 背包 dp 问题, 那么我们在这里就不妨讨论一下常见的几种背包的 dp 问题;
背包问题 一 、 0 ~ 1 背包问题
实现一、 return max ( rec ( i + 1, j ) , rec ( i + 1, j - cost[ i ]) + value[ i ]); //对第 i 件物品的选或者不选
记忆化、 这个是由于实现一产生的dp数组 : dp[ i ] [ j ] : 首先注意这个 i 表示的是第 i 件物品, j 表示的是剩余的价值, 那么这个整体的 dp 表示的就是 i 件物品到 最后一个物品 n - 1 他能够产生的最大价值;
同样我们利用那个递归关系,我们可以知道
dp[ i ] [ j ] = max ( dp[ i + 1] [ j ] , dp[ i + 1] [ j - w[ i ] ] + value [ i ]) ;
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 #include <iostream> 2 #include <cstdio> 3 #include <cstdlib> 4 #include <cstring> 5 #include <cmath> 6 #include <cctype> 7 #include <algorithm> 8 #include <vector> 9 #include <queue> 10 #include <list> 11 #include <map> 12 #include <stack> 13 #include <set> 14 #include <string> 15 16 using namespace std; 17 const int MAX_N = 1010; 18 const int MAX_W = 1010; 19 int dp[MAX_N][MAX_W]; 20 int N, W; 21 int w[MAX_N], value[MAX_N]; 22 int main() 23 { 24 cin>>N>>W; 25 for(int i = 0; i < N; i++) cin>>w[i]>>value[i]; 26 dp[N][0] = 0; 27 fill(dp[N], dp[N] + W, 0); 28 for(int i = N - 1; i >= 0; i--){ 29 for(int j = 0; j <= W; j++){ 30 if(j < w[i]) 31 dp[i][j] = dp[i+1][j]; 32 else 33 dp[i][j] = max(dp[i+1][j], dp[i+1][j-w[i]] + value[i]); 34 } 35 } 36 return 0; 37 }
如果你觉着这样逆着推导有点不舒服的话, 我们可以简单的对dp 数组换一下定义, 我们从原来的 dp [i] [j] 使用由 i 到最后; 同样dp[ i + 1 ] [ j ]我们可以设定为 从 0 到 i 这 i + 1 个物品, 但是你需要注意了;
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 #include <iostream> 2 #include <cstdio> 3 #include <cstdlib> 4 #include <cstring> 5 #include <cmath> 6 #include <cctype> 7 #include <algorithm> 8 #include <vector> 9 #include <queue> 10 #include <list> 11 #include <map> 12 #include <stack> 13 #include <set> 14 #include <string> 15 16 using namespace std; 17 const int MAX_N = 1010; 18 const int MAX_W = 1010; 19 int dp[MAX_N][MAX_W]; 20 int N, W; 21 int w[MAX_N], value[MAX_N]; 22 int main() 23 { 24 cin>>N>>W; 25 for(int i = 0; i < N; i++) cin>>w[i]>>value[i]; 26 fill(dp[0], dp[0] + W, 0); 27 for(int i = 0; i < N; i++){ 28 for(int j = 0; j <= W; j++){ 29 if(j < w[i]) 30 dp[i+1][j] = dp[i][j]; 31 else 32 dp[i+1][j] = max(dp[i][j], dp[i][j-w[i]] + value[i]); 33 } 34 } 35 printf("THE RESULT : %d\n", dp[N][W]); 36 return 0; 37 }
最长公共子序列问题:
那么我们完全可以把看看成追赶问题, 而且联想 dp 背包问题的 i + 1而不是 i + 1 的巧妙设定, 这样防止了数组下标的越界!
dp[ i + 1] [ j + 1] : 这个指的是 s0[ i ] 与 s0[ j ] 到这两个字符串的这两个元素的时候, 他目前的最长公共子序列长度的问题
那我们利用追赶问题的方法进行寻找上下级别的关系:
dp[ i + 1] [ j + 1] = dp[ i ] [ j ] + 1; 这个是当且仅当 s0[ i ] = s1[ j ] ;
当不满足这个等式的时候
dp [ i + 1] [ j + 1] = max ( dp[ i + 1] [ j ], dp[ i ] [ j +1]) //这个就是因为最后两个不相等, 不可能同时使用, 所以说我们删去一个 , 还可以减小问题的规模!
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 #include <iostream> 2 #include <cstring> 3 #include <bits/stdc++.h> 4 #include <algorithm> 5 #include <cstdio> 6 #include <cstdlib> 7 #include <string> 8 #include <cctype> 9 #include <cmath> 10 using namespace std; 11 const int MAX_S = 1010; 12 char s0[MAX_S], s1[MAX_S]; 13 int len1, len0; 14 int dp[MAX_S][MAX_S]; 15 int main() 16 { 17 cin>>s0>>s1; 18 len0 = strlen(s0); 19 len1 = strlen(s1); 20 fill(dp[0], dp[0] + MAX_S, 0); 21 for(int i = 0; i < len0; i++){ 22 for(int j = 0; j < len1; j++){ 23 if(s0[i] == s1[j]){ 24 dp[i+1][j+1] = dp[i][j] + 1; 25 } 26 else{ 27 dp[i+1][j+1] = max(dp[i+1][j], dp[i][j+1]); 28 } 29 } 30 } 31 printf("THE ANSWER IS THAT : %d\n", dp[len0][len1]); 32 return 0; 33 } 34 35 /* 36 abcd 37 becd 38 39 40 */ 41
终于到了兄弟们最喜欢的完全背包问题了 :
那么对于这个类似于 0 1背包, 但是数量无限的情况下, 我们如何进行递归呢?
最简单的、 最容易想的就是仿照我们的0 1 背包进行求解
为了保持正向进行, 我们的 dp [ i + 1] [ j ] 表示的是在处理第 i 件产品, 结果是 0 - i 种物品合理选择后的最大价值量 !
因此我们有 dp[ i + 1] [ j ] = max(dp [ i ] [ j - k * w[ i ] ] + k * value[ i ] ) 注意这个是要保证下标索引是 大于零 的, 而且我们的 k 是自然数 ;
但是不得不说这样的算法复杂度太高, 那么我们怎么进一步化简??
dp[ i + 1] [ j ] = max(dp [ i ] [ j ], dp[ i + 1] [ j - w[ i ]] + value[ i ]);
注意了, 调用这个式子的时候一定要注意, 这个下标索引大于零, 而且初始化
dp[ i + 1] [ j ] = max(dp [ i ] [ j - k * w[ i ] ] + k * value[ i ] ) k >= 0;
= max(dp[ i ] [ j ] , dp [ i ] [ j - k * w[ i ] ] + k * value[ i ] ) k >= 1;
= max(dp[ i ] [ j ] , dp [ i ] [ j - w[ i ] - k * w[ i ] ] + k * value[ i ] + value[ i ]) k >= 0;
= max(dp[ i ] [ j ] , dp [ i + 1 ] [ j - w[ i ] ] + value[ i ]);
那么这样的证明就是好起来了。
0-1 背包问题二
多重部分和问题
最长上升子序列问题
最简单的第一种算法, 就是把它抽象为追赶问题, 而且
dp [ j ] = max ( dp [ j ] , dp [ i ] + 1 ); i < j 而且 a [ i ] < a [ j ]; 有些问题, 我们需要的是最长下降子序列的问题
代码
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 /* 2 将本题目抽象出来居然是 最长上升子序列问题 3 */ 4 #include <iostream> 5 #include <cstdio> 6 #include <cstdlib> 7 #include <cstring> 8 #include <cmath> 9 #include <cctype> 10 #include <algorithm> 11 #include <vector> 12 #include <queue> 13 #include <list> 14 #include <map> 15 #include <stack> 16 #include <set> 17 #include <string> 18 19 using namespace std; 20 const int MAX_N = 4e4+10; 21 int a[MAX_N]; 22 int dp[MAX_N]; 23 24 int main() 25 { 26 int T; cin>>T; 27 while(T--){ 28 int n; cin>>n; 29 for(int i = 0; i < n; i++){ 30 scanf("%d", &a[i]); 31 } 32 memset(dp, 0, sizeof(dp)); 33 int res = 0; 34 dp[0] = 1; 35 for(int i = 1; i < n; i++){ 36 dp[i] = 1; 37 for(int j = 0; j < i; j++){ 38 if(a[i] > a[j]){ 39 dp[i] = max(dp[i], dp[j] + 1); 40 res = max(res, dp[i]); 41 } 42 } 43 } 44 printf("THE RESULT :\n"); 45 printf("%d\n", res); 46 } 47 return 0; 48 }
下一个dp [i] 就更牛逼了, 甚至可以使用二分查找; 真的是好用!!
(注意下标存取的是长度, 然后dp [ i ] 存储是传说当中的当前长度的最小尾部元素)
lower_bound : ai >= k 的最小指针
upper_bound : ai >k 的最小指针
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 /* 2 将本题目抽象出来居然是 最长上升子序列问题 3 */ 4 #include <iostream> 5 #include <cstdio> 6 #include <cstdlib> 7 #include <cstring> 8 #include <cmath> 9 #include <cctype> 10 #include <algorithm> 11 #include <vector> 12 #include <queue> 13 #include <list> 14 #include <map> 15 #include <stack> 16 #include <set> 17 #include <string> 18 19 using namespace std; 20 const int MAX_N = 4e4+10; 21 const int INF = 1e5; 22 int a[MAX_N]; 23 int dp[MAX_N]; 24 //然后里面的dp换了含义, dp[i] 代表的是长度为 i 的最小尾部, 25 //那么我们就可以依靠这个进行不断地更新 26 27 int main(void){ 28 int T; cin>>T; 29 while(T--){ 30 int n; cin>>n; 31 for(int i = 0; i < n; i++){ 32 scanf("%d", &a[i]); 33 } 34 fill(dp, dp + n, INF); 35 for(int i = 0; i < n; i++){ 36 *lower_bound(dp, dp + n, a[i]) = a[i]; 37 } 38 // printf("THE RESULT IS :\n"); 39 printf("%d\n", lower_bound(dp, dp + n, INF) - dp); 40 } 41 return 0; 42 }