【TOJ1765】最长上升子序列
状态表示:dp[i] 以a[i]为数列的最后一个数时最长上升子序列的长度
状态转移方程:if(a[i] >a[k])dp[i] = max(dp[i],dp[k]+1); 1<=k<=i-1;
代码:
#include <cstdio> #include <cstring> using namespace std; #define max(a,b) (a) > (b) ? (a) : (b) const int MAXN = 1010; int a[MAXN],dp[MAXN]; int main(){ int N; while(scanf("%d",&N) != EOF){ for(int i = 1; i <= N; ++i){ scanf("%d",&a[i]); dp[i] = 1; } for(int i = 1; i <= N; ++i) for(int k = 1; k <= i - 1; ++k){ if(a[i] > a[k]){ dp[i] = max(dp[i],dp[k] + 1); } } int ans = 0; for(int i = 1; i <= N; ++i){ ans = max(ans,dp[i]); } /****************************/ //输出找到的最长上升子序列 /* int anss = ans; for(int i = N; i >= 1; --i){ if(dp[i] == anss){ anss --; printf("%d ",a[i]); } } puts(""); */ /***************************/ printf("%d\n",ans); } return 0; }
LIS(最长上升子序列)是比较简单的一道经典DP,另外还有LCS(最长公共子序列),还有一个较难的,LCIS(最长上升公共子序列)
【POJ2127】Greatest CommonIncreasing Subsequence
解析:定义max[i][j]为A串匹配到i,B串匹配到j时的最长公共上升子序列的长度。
若A[i]!=B[j] 那么max[i][j]=MAX{max[i'][j']|i'<i,j'<j}
若A[i]==B[j] 那么max[i][j]=MAX{max[i'][j']+1|i'<i,j'<j,A[i']<A[i],B[j']<B[j]}
这一个O(n^4) 的算法,但是我们会发现max[i][j]长度会增加1的时候都是在A[i]==B[j]情况下,那么我们应该考虑如何应用这个性质呢。所以我们以A串为基准,重新定义max[i][j]:以A[i]为基准匹配串末尾时(A[i]必选),匹配到B[j]时的最长公共上升子序列的长度
若A[i]!=B[j] 那么max[i][j]=max[i][j-1]
若A[i]==B[j] 那么max[i][j]=MAX{max[i'][j-1]+1|i'<i,A[i']<A[i]}
通过这样的定义我们保证了,对于任何max[i][j],max[i][j]里面存放的是已当前A[i]为匹配的串的最后一个元素时的最大长度。我们可以看到这个方程的复杂度只有O(n^3) ,对于j的循环就没有必要了。
代码:
#include<iostream> using namespace std; int a[505],b[505]; int dp[505][505]; int n,m; int path[505][505]; int I,J,ans,res[505]; int main() { int i,j,k; while(scanf("%d",&n)!=-1) { ans=0; for(i=0;i<n;i++) scanf("%d",&a[i]); scanf("%d",&m); for(i=0;i<m;i++) scanf("%d",&b[i]); memset(path,-1,sizeof(path)); memset(dp,0,sizeof(dp)); for(i=1;i<=n;i++) for(j=1;j<=m;j++) { dp[i][j]=dp[i-1][j]; if(a[i-1]==b[j-1]) { int max=0; for(k=1;k<j;k++) if(b[k-1]<b[j-1]&&dp[i][max]<dp[i][k]) max=k; if(dp[i][max]+1>dp[i][j]) { dp[i][j]=dp[i][max]+1; path[i][j]=max; if(dp[i][j]>ans) { ans=dp[i][j]; I=i; J=j; } } } } printf("%d\n",ans); k=ans; while(ans) { if(path[I][J]>=0) { res[ans--]=b[J-1]; J=path[I][J]; } I--; } for(i=1;i<=k;i++) printf("%d ",res[i]); printf("\n"); } }
【NKU1137】石子合并问题
题目大意:在一个圆形操场的四周摆放着n 堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。试设计一个算法,计算出将n堆石子合并成一堆的最小得分和最大得分。
解析:如果这些石子一条直线排列,那么可以将1到n的合并拆分为两个结构相同的1-k的一堆和k+1到n的一堆石子合并的问题,就可以用dp[i][j]表示合并i到j的石子所能得到的最优值。但本题的石子围城了一个圈,就必须改变dp数组的设计
状态表示:dp[i][j]表示从第i块石头开始数起,合并j块石头所能得到的最优值
状态转移方程:dp[i][j] =better(dp[i][j],dp[i][i+k]+dp[(i+k-1)%n+1][j-k]+sum[i][j]);(1<=k<=j-1)
初始态(DP边界):dp[i][1] = 0;
代码:
#include <cstdio> #include <cstring> using namespace std; #define INF 0x1f1f1f #define max(a,b) (a) > (b) ? (a) : (b) #define min(a,b) (a) < (b) ? (a) : (b) const int MAXN = 110; int sum[MAXN][MAXN]; //sum[i][j]表示从i开始数j个石头重量的和 int dp[MAXN][MAXN]; //dp[i][j]表示从i开始数j个石头合并以后的最大得分 int dp1[MAXN][MAXN]; int n,val[MAXN]; int main(){ while(scanf("%d",&n) != EOF){ for(int i = 1; i <= n; ++i)scanf("%d",&val[i]); for (int i = 1; i <= n; ++i) sum[i][1] = val[i]; for (int j = 2; j <= n; ++j) for (int i = 1; i <= n; ++i) sum[i][j] = val[i] +sum[(i+1-1)%n+1][j - 1]; for(int i = 0; i <= n; ++i)dp[i][1] = dp1[i][1] = 0; for(int j = 2; j <= n; ++j){ //这里必须把j放到外层以保证用到的时候已经求出 for(int i = 1; i <= n; ++i){ dp[i][j] = 0; dp1[i][j] = INF; for(int k = 1; k < j; ++k){ dp1[i][j] =min(dp1[i][j],dp1[i][k] + dp1[(i+k-1)%n+1][j-k]+sum[i][j]); dp[i][j] =max(dp[i][j],dp[i][k] + dp[(i+k-1)%n+1][j-k]+sum[i][j]); //此处用到的是dp[][j-k],所以要保证dp[1..n][j-k]已经求出,所以j放循环外层 } } } int ans = 0,ans1 = INF; for(int i = 1; i <= n; ++i){ ans = max(ans,dp[i][n]); ans1 = min(ans1,dp1[i][n]); } printf("%d\n",ans1); printf("%d\n",ans); } return 0; }
【POJ1160】POST OFFICE 邮局问题
【题目大意】:用数轴描述一条高速公路,有V个村庄,每一个村庄坐落在数轴的某个点上,需要选择P个村庄在其中建立邮局,要求每个村庄到最近邮局的距离和最小。
【题目分析】:经典DP
1、考虑在V个村庄中只建立【一个】邮局的情况,显然可以知道,将邮局建立在中间的那个村庄即可。也就是在a到b间建立一个邮局,若使消耗最小,则应该将邮局建立在(a+b)/2这个村庄上(可以通过画图知道)。
2、下面考虑建立【多个】邮局的问题,可以这样将该问题拆分为若干子问题,在前i个村庄中建立j个邮局的最短距离,是在前【k】个村庄中建立【j-1】个邮局的最短距离 与 在【k+1】到第i个邮局建立【一个】邮局的最短距离的和。而建立一个邮局我们在上面已经求出。
3、状态表示,由上面的讨论,可以开两个数组
dp[i][j]:在前i个村庄中建立j个邮局的最小耗费
sum[i][j]:在第i个村庄到第j个村庄中建立1个邮局的最小耗费
那么就有转移方程:dp[i][j] =min(dp[i][j],dp[k][j-1]+sum[k+1][i]) DP的边界状态即为dp[i][1] = sum[1][i]; 所要求的结果即为dp[vil_num][post_num];
4、然后就说说求sum数组的优化问题,可以假定有6个村庄,村庄的坐标已知分别为p1,p2,p3,p4,p5,p6;那么,如果要求sum[1][4]的话邮局需要建立在2或者3处,放在2处的消耗为p4-p2+p3-p2+p2-p1=p4-p2+p3-p1 放在3处的结果为p4-p3+p3-p2+p3-p1=p4+p3-p2-p1,可见,将邮局建在2处或3处是一样的。现在接着求sum[1][5],现在处于中点的村庄是3,那么1-4到3的距离和刚才已经求出了,即为sum[1][4],所以只需再加上5到3的距离即可。同样,求sum[1][6]的时候也可以用sum[1][5]加上6到中点的距离。所以有递推关系:sum[i][j] = sum[i][j-1] + p[j] -p[(i+j)/2]
代码:
#include <cstdio> #include <cstring> using namespace std; #define min(a,b) (a) < (b) ? (a) : (b) int dp[310][31]; int sum[310][310]; int V,P; int pos[310]; int main(){ while(scanf("%d%d",&V,&P) != EOF){ //少写了一个%d直接造成结果不对,晕死 for(int i = 1; i <= V; ++i)scanf("%d",&pos[i]); memset(sum,0,sizeof(sum)); for(int i=1 ; i< V ; i++){ for(int j=i+1 ; j<= V ; j++){ sum[i][j] = sum[i][j-1]+ pos[j] - pos[(i+j) / 2]; } } for(int i = 1; i <= V; ++i){ dp[i][i] = 0; dp[i][1] = sum[1][i]; } for(int j = 2; j <= P; ++j){ //注意为什么把它放在外层 for(int i = j+1; i <= V; ++i){ dp[i][j] = 9999999; for(int k = j-1; k < i; ++k) dp[i][j] =min(dp[i][j],dp[k][j-1]+sum[k+1][i]); } } printf("%d\n",dp[V][P]); } return 0; }