经典动态规划问题
1. 最长上升子序列
HDU1257 最长上升子序列
题目:http://acm.hdu.edu.cn/showproblem.php?pid=1257
算法: 设dp[i]表示长度为i的序列的最后一个数字,dp[0] = 0,
对于num[j],我们可以枚举dp[j-1..1]比其小的数字,假设dp[k] 比其小,那么我们就num[k] 更新dp[k+1], dp[k+1] = num[k]
同时可知,dp[i]是单调递增的序列. 所以求最长上升子序列有o(N*N) 和 O( n logn )两种算法
代码1: O(N*N)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <algorithm> using namespace std; int dp[1100]; const int inf = 0x7f7f7f7f; //最长上升子序列 /* 状态转移方程 dp[i]表示长度为i的最长上升序列最末的那个数 o( n^2) 的时间复杂度 */ int main( ) { int N,num[1000]; while( scanf("%d",&N) != EOF ) { for(int i = 1; i <= N; ++i) scanf("%d",&num[i]); for(int i = 1; i <= N; ++i) dp[i] = inf; dp[0] = 0; int maxn = 0; for(int i = 1; i <= N; ++i) { if( num[i] > dp[i-1] ) { dp[i] = num[i]; if( i > maxn ) maxn = i; } else { for(int j = i-1; j >= 0; --j) { if( num[i] > dp[j] ) { dp[j+1] = min(dp[j+1],num[i]); if( j + 1 > maxn ) maxn = j + 1; break; } } } } printf("%d\n",maxn); } return 0; }
代码2:O(N LogN)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <algorithm> using namespace std; int dp[1100]; const int inf = 0x7f7f7f7f; //最长上升子序列 /* 状态转移方程 dp[i]表示长度为i的最长上升序列最末的那个数 o( n^2) 的时间复杂度 */ int main( ) { int N,num[1000]; while( scanf("%d",&N) != EOF ) { for(int i = 1; i <= N; ++i) scanf("%d",&num[i]); memset(dp, 0, sizeof(dp)); int maxn = 0; //dp[i]为单调递增序列 dp[1] = num[1]; int len = 1; for(int i = 2; i <= N; ++i) { int x = lower_bound(dp + 1, dp + len + 1, num[i]) - dp; //printf("x = %d num[%d] = %d\n",x, i, num[i]); if( x > len ) { ++len;dp[len] = num[i];} else { dp[x] = num[i];} } printf("%d\n",len); } return 0; }
2. 最长公共子序列问题
HDU 1159
题目:http://acm.hdu.edu.cn/showproblem.php?pid=1159
题意: 求两个字符串最长公共元素个数
算法: dp 方程, f[i][j] 表示序列A前i个元素和序列B前j个元素 最长公共元素个数
f[i][j] = f[i-1][j-1] + 1 ( a[i] = b[j])
f[i][j] = max(f[i-1][j],f[i][j-1]) ( a[i] != b[j])
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <algorithm> using namespace std; char a[210]; char b[210]; int f[210][210]; /* 最长公共子序列 f[i][j] 表示序列前i个数与序列前j个数最长的个数 if( a[i] == b[j] ) f[i][j] = f[i-1][j-1] + 1; else f[i][j] = max(f[i-1][j], f[i][j-1]) */ int main( ) { int N,T; while( scanf("%s%s",a + 1 ,b + 1) != EOF ) { memset(f, 0, sizeof(f)); int len1 = strlen(a + 1); int len2 = strlen(b + 1); for(int i = 1; i <= len1; ++i) { for(int j = 1; j <= len2; ++j) { if( a[i] == b[j] ) f[i][j] = f[i-1][j-1] + 1; else f[i][j] = max(f[i-1][j], f[i][j-1]); } } printf("%d\n",f[len1][len2]); } return 0; }
3. 动态规划的选择策略追踪,记录
以最长公共子序列为例
record[i][j] = T 表示f[i][j] 由 f[i-1][j-1]递推而来
record[i][j] = L 表示f[i][j] 由 f[i-1][j] 递推而来
record[i][j] = R 表示f[i][j] 由 f[i][j-1]递推而来
输出结果可用递归,也可用while循环迭代
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <algorithm> #include <stack> using namespace std; char a[210]; char b[210]; int f[210][210]; int record[210][210]; #define T 1 #define L 2 #define R 3 /* 最长公共子序列 f[i][j] 表示序列前i个数与序列前j个数最长的个数 if( a[i] == b[j] ) f[i][j] = f[i-1][j-1] + 1; else f[i][j] = max(f[i-1][j], f[i][j-1]) record[i][j] = T 表示f[i][j] 由 f[i-1][j-1]递推而来 record[i][j] = L 表示f[i][j] 由 f[i-1][j] 递推而来 record[i][j] = R 表示f[i][j] 由 f[i][j-1]递推而来 */ //递归输出所有方案 void print(int i, int j) { if( i <= 0 || j <= 0 ) return; if( record[i][j] == T ) { print(i-1,j-1); printf("%c ",a[i],b[j]); } else if( record[i][j] == L) print(i-1,j); else if( record[i][j] == R ) print(i, j-1); } void print_reversewhile(int i, int j) { stack<char>pp; while( !((i == 0) && (j == 0)) ) { if( record[i][j] == T ) { printf("%c ",a[i]); pp.push(a[i]); i--,j--; } else if( record[i][j] == L) { i--; } else if( record[i][j] == R ) { j--; } } puts("\n顺序输出"); while( !pp.empty() ) printf("%c ",pp.top()), pp.pop(); puts(""); } int main( ) { int N; while( scanf("%s%s",a + 1 ,b + 1) != EOF ) { memset(f, 0xff, sizeof(f)); memset(record, 0, sizeof(record)); for(int i = 0; i <= 200; i++) f[i][0] = 0; int len1 = strlen(a + 1); int len2 = strlen(b + 1); for(int i = 1; i <= len1; ++i) { for(int j = 1; j <= len2; ++j) { if( a[i] == b[j] ) { f[i][j] = f[i-1][j-1] + 1; record[i][j] = T; } else { if( f[i-1][j] > f[i][j-1] ) { f[i][j] = f[i-1][j]; record[i][j] = L; } else { f[i][j] = f[i][j-1]; record[i][j] = R; } } } } printf("%d\n",f[len1][len2]); printf("递归输出:\n"); print(len1,len2); puts("\n迭代逆序输出:\n"); print_reversewhile(len1,len2); } return 0; } /* 输入输出: abcfbc abfcab 4 递归输出: a b f c 迭代逆序输出: c f b a 顺序输出 a b f c hello,iamtangcong howareyouwhatisyourname 7 递归输出: h e o a t o n 迭代逆序输出: n o t a o e h 顺序输出 h e o a t o n */
4.矩阵链乘法
题目:http://ac.nbutoj.com/Problem/view.xhtml?id=1003
矩阵链乘法。。。
动态规划方程/。。。
mt[i][j] 表示从第i个矩阵到第j个矩阵,所用最少乘法次数
mt[i][j] = mt[i][k] + mt[k +1 ][j] + p[i-1] * p[k] * p[j];
mt[i][i] = 0
自底向上的递推,当然也可以采用记忆化搜索,不过因为递归等,所以性能上会差点
1 1 2 2 3 3 4 4 ....
1 2 2 3 3 4 4 5 ....
1 3 2 4 3 5 4 6 5 7 6 8 7 9 ....
输出方案:
f[i][j] = t; //表是在t位置截断 , 才用递归输出方案
代码1:这种写法有点差~~
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <algorithm> using namespace std; int p[210]; int mt[210][210]; /* 矩阵链乘法。。。 动态规划方程/。。。 mt[i][j] 表示从第i个矩阵到第j个矩阵,所用最少乘法次数 mt[i][j] = mt[i][k] + mt[k +1 ][j] + p[i-1] * p[k] * p[j]; mt[i][i] = 0 自底向上的递推 mt[1][1] mt[1][2] mt[2][2] mt[2][3] mt[3][3] mt[1][3] 1 1 2 2 3 3 4 4 .... 1 2 2 3 3 4 4 5 .... 1 3 2 4 3 5 4 6 5 7 6 8 7 9 .... 1 4 2 5 3 6 */ int Matrix(int N) { for(int i = 1, k = 1; i < N; i++) { int t = i + k; //printf("k = %d\n",k); for(int j = 1; j < N && t <= N && (j+k) <= N; j += k, t += k) { if( k== 1 && t-j == 1) { mt[j][t] = mt[j][j+k] + p[j-1] * p[j] * p[t]; // printf("mt[%d][%d] = %d mt[%d][%d] = %d %d %d %d\n",j,t,mt[j][t], j, j + k, mt[j][j + k], p[j-1], p[j], p[t]); } else { //puts("a"); if( (j + k+1) <= t ) { //if( mt[j][t] == 0 ) for(int l = j; l < t; ++l) { if( mt[j][t] == 0 ) mt[j][t] = mt[j][l] + mt[l+1][t] + p[j-1] * p[l] * p[t]; else mt[j][t] = min(mt[j][l] + mt[l+1][t] + p[j-1] * p[l] * p[t], mt[j][t]); // printf("mt[%d][%d] = %d mt[%d][%d] = %d mt[%d][%d] = %d %d %d %d\n",j,t,mt[j][t], j, l, mt[j][j + k], l+1, t, mt[l+ 1][t], p[j-1], p[l], p[t]); } } } } //++k; } return mt[1][N]; } int main( ) { int N; while( scanf("%d",&N) != EOF ) { memset(p, 0, sizeof(p)); for(int i = 0; i <= N; ++i) scanf("%d",&p[i]); memset(mt, 0 ,sizeof(mt)); printf("%d\n",Matrix(N)); } return 0; }
代码2:并且输出方案
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <stdio.h> #include <string.h> #include <stdlib.h> using namespace std; int p[210]; int mt[210][210]; int f[210][210]; const int inf = 0x7f7f7f7f; int matrix(int n) { for(int i = 0; i <= n; i++) mt[i][i] = 0; for(int l = 2; l <= n; ++l) { for(int i = 1; i <= n - l + 1; ++i ) { int j = i + l - 1; mt[i][j] = inf; //printf("%d %d\n",i,j); for(int k = i; k <= j-1; k++) { int ans = mt[i][k] + mt[k+1][j] + p[i-1] * p[k] * p[j]; if( ans < mt[i][j] ) { mt[i][j] = ans; f[i][j] = k; } // printf("mt[%d][%d] = %d, mt[%d][%d] = %d, mt[%d][%d] = %d\n", i,j,mt[i][j],i,k,mt[i][k],k+1,j,mt[k+1][j]); } } } return mt[1][n]; } void print(int a, int b) { if( a == b ) printf("A%d",a); else { printf("("); print(a,f[a][b]); print(f[a][b] + 1, b); printf(")"); } } int main( ) { int N; while( scanf("%d",&N) != EOF ) { for(int i = 0; i <= N; ++i) scanf("%d",&p[i]); printf("%d\n",matrix(N)); //puts("方案"); //print(1,N); //puts(""); } return 0; }
5. 编辑距离
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <stdio.h> #include <string.h> #include <stdlib.h> char str1[100], str2[100]; int dp[100][100]; int min(int x, int y) { return x < y ? x : y; } int EDIT( ) { //str1为目标串,str2为原串 int len1 = strlen(str1 + 1); int len2 = strlen(str2 + 1); for(int i = 1; i <= len1; i++) dp[i][0] = i; //insert str1[i] for(int i = 1; i <= len2; i++) dp[0][i] = i; //delete str2[i] for(int i = 1; i <= len1; i++) { for(int j = 1; j <= len2; j++) { //dp[i][j] = dp[i-1][j-1] + str1[i] == str2[j] ; 替换 //dp[i][j] = dp[i-1][j] + 1, delete str1[i] //dp[i][j] = dp[i][j-1] + 1; insert s[j] dp[i][j] = min( dp[i-1][j-1] + (str1[i] != str2[j] ) , min( dp[i-1][j] + 1, dp[i][j-1] + 1) ); } } return dp[len1][len2]; } int main( ) { while( scanf("%s%s",str1 + 1, str2 + 1) != EOF) { memset(dp, 0, sizeof(dp)); printf("%d\n", EDIT()); } return 0; }