简单dp问题总共分为四类:找钱问题,01背包问题,最长公共子序列问题,最长递增子序列问题
------------------------------------------------------------------------------------------------------------------------------------------
找钱问题
这类问题是dp中最基础的问题,其形式是最外层循环为钱的种类,第二层是钱的金额,最末层是用多少数量的钱实现
但是核心是建立在后续能用前面的值推出的基础上。
dp[j][k] = dp[j][k] + dp[j - 1][k - money[i]];
j表示用多少张钱,k表示目前的金额
这种问题要先知道可用的钱币是什么,然后先把他存在数组里
1 #include <iostream> 2 #include <cstring> 3 using namespace std; 4 int money[5] = { 1,5,10,25,50 }; 5 long long int dp[300][300] = {0}; 6 int ans[300] = {0}; 7 int main() 8 { 9 int n; 10 memset(dp, 0, sizeof(dp)); 11 dp[0][0] = 1;//切记是从dp[0][0]开始的 12 for (int i = 0; i < 5; i++) 13 { 14 for (int j = 1; j <= 100; j++) 15 { 16 for (int k = money[i]; k <252; k++)//之前开太大了,爆栈 17 { 18 dp[j][k] = dp[j][k] + dp[j - 1][k - money[i]]; 19 20 } 21 } 22 } 23 24 for (int i = 0; i <252; i++) 25 { 26 ans[i] = 0; 27 for (int j = 0; j <= 100; j++) 28 { 29 ans[i] += dp[j][i]; 30 } 31 } 32 while (cin >> n) cout << ans[n] << endl; 33 return 0; 34 }
------------------------------------------------------------------------------------------------------------------------------------------
0/1背包问题
这问题核心就是控制背包容量,然后问当前物品装还是不装的问题
纵向是第几个物品
横向是背包容量
dp[j][k] = max(dp[j - 1][k], dp[j - 1][k - bone[j].volume] + bone[j].value);
不装就和前一个情况一样,装就要在当前背包容量减去所要装的骨头体积的情况下再加上此骨头的价值
附加:如果背包的数为小数,比如两位小数,则对每个数*100,把他提升为整数问题(hdu1864)
1 #include <cstdlib> 2 #include <cctype> 3 #include <iterator> 4 #include <vector> 5 #include <cstring> 6 #include <cassert> 7 #include <map> 8 #include <queue> 9 #include <set> 10 #include <stack> 11 #include <stdio.h> 12 #define ll long long 13 #define INF 0x3f3f3f3f 14 #define ld long double 15 const ld pi = acos(-1.0L), eps = 1e-8; 16 using namespace std; 17 int dp[1010][1010]; 18 struct Bone 19 { 20 int value, volume; 21 }; 22 Bone bone[1010]; 23 int main() 24 { 25 ios::sync_with_stdio(false); 26 cin.tie(0); 27 int n; 28 cin >> n; 29 for (int i = 1; i <= n; i++) 30 { 31 memset(dp, 0, sizeof(dp)); 32 int num, volume; 33 cin >> num >> volume; 34 for (int j = 1; j <= num; j++) cin >> bone[j].value; 35 for (int j = 1; j <= num; j++)cin >> bone[j].volume; 36 37 for (int j = 1; j <= num; j++) 38 { 39 for (int k = 0; k <= volume; k++) 40 { 41 if (bone[j].volume > k)dp[j][k] = dp[j - 1][k];//如果当前骨头都比被背包大的话,那肯定不能放进去的 42 else dp[j][k] = max(dp[j - 1][k], dp[j - 1][k - bone[j].volume] + bone[j].value); 43 } 44 } 45 46 cout << dp[num][volume] << endl; 47 } 48 }
------------------------------------------------------------------------------------------------------------------------------------------
最长公共子序列问题
感觉这个可以背下来
遍历一下,如果元素相同,就找dp[i-1][j-1]的那个值+1;
否则就找dp[i][j-1]和dp[i-1][j]中最大的
1 #include <iostream> 2 #include <cmath> 3 #include <algorithm> 4 #include <cstring> 5 #include <string> 6 using namespace std; 7 int dp[1005][1005]; 8 string s1, s2; 9 int main() 10 { 11 12 while (cin >> s1 >> s2) 13 { 14 memset(dp, 0, sizeof(dp)); 15 for (int i = 1; i <= s1.size(); i++) 16 { 17 for (int j = 1; j <= s2.size(); j++) 18 { 19 if (s1[i - 1] == s2[j - 1]) 20 { 21 dp[i][j] = dp[i - 1][j - 1] + 1; 22 } 23 else 24 { 25 dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]); 26 } 27 } 28 } 29 cout << dp[s1.size()][s2.size()] << endl; 30 } 31 }
------------------------------------------------------------------------------------------------------------------------------------------
最长递增子序列
用两个循环定位
第一个循环定位当前值i
第二个循环依次循环j从开始到当前值,只要该值比当前固定的值(第一层循环)小,
那么就检查以它为结尾的最长递增子序列长度是多少,找出最长的那个子序列长度,
dp[i] = maxi + 1;
1 #include <iostream> 2 using namespace std; 3 int st[10000]; 4 int dp[10000] = { 0 }; 5 int main() 6 { 7 int n; 8 cin >> n; 9 for (int i = 1; i<=n; i++) cin >> st[i]; 10 dp[1] = 1;//第一个数最长只能为1 11 int ans = 1; 12 for (int i = 2; i <= n; i++) 13 { 14 int maxi = 0; 15 for (int j = 1; j < i; j++) 16 { 17 if (dp[j] >maxi &&st[j]>st[i]) maxi = dp[j]; 18 } 19 dp[i] = maxi + 1;//如果该数前面的数都不满足,那么他的长度就是1,从他自己开始 20 if (dp[i] > ans) ans = dp[i];//找出最大的 21 } 22 cout << ans << endl; 23 }
优化:
可以添加一个辅助数组,来存最长递增子序列
辅助数组先存入原数组的第一个,之后依次遍历原数组的每一个值
如果原数组的当前遍历值大于辅助数组的最后一个值,那么当前遍历值直接加入辅助数组,更新辅助数组的最后一个值
如果原数组的当前遍历值小于等于辅助数组的最后一个值,那么替换辅助数组中第一个大于等于该遍历值的值
比如原数组 1 3 6 5
那么辅助数组依次为 1/1,3/1,3,6/1,3,5
最后最长递增子序列的长度即为辅助数组的大小
附个题目:http://acm.hdu.edu.cn/showproblem.php?pid=1257
代码:
#include <iostream> #include <algorithm> #include <cstring> using namespace std; int num[100010],arr[100010]; int main() { int n; while (cin >> n) { memset(arr, 0, sizeof(arr)); for (int i = 1; i <= n; i++) { cin >> num[i]; } arr[1] = num[1]; int len = 1; for (int i = 2; i <= n; i++) { if (num[i] > arr[len]) { len++; arr[len] = num[i]; } else { int j = lower_bound(arr+ 1, arr + len + 1, num[i]) - arr; arr[j] = num[i]; } } cout << len << endl; } }