【TOJ1765】最长上升子序列
状态表示:dp[i] 以a[i]为数列的最后一个数时最长上升子序列的长度
状态转移方程:if(a[i] >a[k])dp[i] = max(dp[i],dp[k]+1); 1<=k<=i-1;
代码:
#include
#include
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'
若A[i]==B[j] 那么max[i][j]=MAX{max[i'][j']+1|i'
这一个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'
通过这样的定义我们保证了,对于任何max[i][j],max[i][j]里面存放的是已当前A[i]为匹配的串的最后一个元素时的最大长度。我们可以看到这个方程的复杂度只有O(n^3) ,对于j的循环就没有必要了。
代码:
#include
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;idp[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
#include
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
#include
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;
}