DP基础问题若干(ACM/ICPC) 来自http://blog.csdn.net/accry/article/details/6607593

【TOJ1765】最长上升子序列

状态表示:dp[i]  以a[i]为数列的最后一个数时最长上升子序列的长度

状态转移方程:if(a[i] >a[k])dp[i] = max(dp[i],dp[k]+1); 1<=k<=i-1;

代码:

  1. #include <cstdio>   
  2. #include <cstring>   
  3. using namespace std;  
  4. #define max(a,b) (a) > (b) ? (a) : (b)   
  5. const int MAXN = 1010;  
  6. int a[MAXN],dp[MAXN];  
  7. int main(){  
  8.    int N;  
  9.    while(scanf("%d",&N) != EOF){  
  10.        for(int i = 1; i <= N; ++i){  
  11.            scanf("%d",&a[i]);  
  12.            dp[i] = 1;  
  13.        }  
  14.        for(int i = 1; i <= N; ++i)  
  15.        for(int k = 1; k <= i - 1; ++k){  
  16.            if(a[i] > a[k]){  
  17.                 dp[i] = max(dp[i],dp[k] + 1);  
  18.            }  
  19.        }  
  20.        int ans = 0;  
  21.        for(int i = 1; i <= N; ++i){  
  22.            ans = max(ans,dp[i]);  
  23.        }  
  24.        /****************************/  
  25.        //输出找到的最长上升子序列   
  26.        /* 
  27.        int anss = ans; 
  28.        for(int i = N; i >= 1; --i){ 
  29.            if(dp[i] == anss){ 
  30.                 anss --; 
  31.                 printf("%d ",a[i]); 
  32.            } 
  33.        } 
  34.        puts(""); 
  35.        */  
  36.        /***************************/  
  37.        printf("%d\n",ans);  
  38.    }  
  39.    return 0;  
  40. }  
#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的循环就没有必要了。

代码:

  1. #include<iostream>   
  2. using namespace std;  
  3. int a[505],b[505];  
  4. int dp[505][505];  
  5. int n,m;  
  6. int path[505][505];  
  7. int I,J,ans,res[505];  
  8. int main()  
  9. {  
  10.    int i,j,k;  
  11.    while(scanf("%d",&n)!=-1)  
  12.    {  
  13.        ans=0;  
  14.        for(i=0;i<n;i++)  
  15.            scanf("%d",&a[i]);  
  16.        scanf("%d",&m);  
  17.        for(i=0;i<m;i++)  
  18.            scanf("%d",&b[i]);  
  19.        memset(path,-1,sizeof(path));  
  20.        memset(dp,0,sizeof(dp));  
  21.        for(i=1;i<=n;i++)  
  22.            for(j=1;j<=m;j++)  
  23.            {  
  24.                 dp[i][j]=dp[i-1][j];  
  25.                 if(a[i-1]==b[j-1])  
  26.                 {  
  27.                     int max=0;  
  28.                     for(k=1;k<j;k++)  
  29.                        if(b[k-1]<b[j-1]&&dp[i][max]<dp[i][k])  
  30.                             max=k;  
  31.                    if(dp[i][max]+1>dp[i][j])  
  32.                     {  
  33.                         dp[i][j]=dp[i][max]+1;  
  34.                         path[i][j]=max;  
  35.                         if(dp[i][j]>ans)  
  36.                         {  
  37.                             ans=dp[i][j];  
  38.                             I=i;  
  39.                             J=j;  
  40.                         }  
  41.                     }  
  42.                 }  
  43.            }  
  44.         printf("%d\n",ans);  
  45.        k=ans;  
  46.        while(ans)  
  47.        {  
  48.            if(path[I][J]>=0)  
  49.            {  
  50.                 res[ans--]=b[J-1];  
  51.                 J=path[I][J];  
  52.            }  
  53.            I--;  
  54.        }  
  55.        for(i=1;i<=k;i++)  
  56.            printf("%d ",res[i]);  
  57.        printf("\n");  
  58.    }  
  59. }  
#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;

代码:

  1. #include <cstdio>   
  2. #include <cstring>   
  3. using namespace std;  
  4. #define INF 0x1f1f1f   
  5. #define max(a,b) (a) > (b) ? (a) : (b)   
  6. #define min(a,b) (a) < (b) ? (a) : (b)   
  7.    
  8. const int MAXN = 110;  
  9. int sum[MAXN][MAXN];  //sum[i][j]表示从i开始数j个石头重量的和   
  10. int dp[MAXN][MAXN];   //dp[i][j]表示从i开始数j个石头合并以后的最大得分   
  11. int dp1[MAXN][MAXN];  
  12. int n,val[MAXN];  
  13.    
  14. int main(){  
  15.    while(scanf("%d",&n) != EOF){  
  16.        for(int i = 1; i <= n; ++i)scanf("%d",&val[i]);  
  17.        for (int i = 1; i <= n; ++i)  
  18.            sum[i][1] = val[i];  
  19.        for (int j = 2; j <= n; ++j)  
  20.            for (int i = 1; i <= n; ++i)  
  21.                 sum[i][j] = val[i] +sum[(i+1-1)%n+1][j - 1];  
  22.        for(int i = 0; i <= n; ++i)dp[i][1] = dp1[i][1] = 0;  
  23.    
  24.        for(int j = 2; j <= n; ++j){ //这里必须把j放到外层以保证用到的时候已经求出   
  25.            for(int i = 1; i <= n; ++i){  
  26.    
  27.                 dp[i][j] = 0;  
  28.                 dp1[i][j] = INF;  
  29.                 for(int k = 1; k < j; ++k){  
  30.                     dp1[i][j] =min(dp1[i][j],dp1[i][k] + dp1[(i+k-1)%n+1][j-k]+sum[i][j]);  
  31.                     dp[i][j] =max(dp[i][j],dp[i][k] + dp[(i+k-1)%n+1][j-k]+sum[i][j]);  
  32.                     //此处用到的是dp[][j-k],所以要保证dp[1..n][j-k]已经求出,所以j放循环外层   
  33.                 }  
  34.            }  
  35.        }  
  36.    
  37.        int ans = 0,ans1 = INF;  
  38.        for(int i = 1; i <= n; ++i){  
  39.            ans = max(ans,dp[i][n]);  
  40.            ans1 = min(ans1,dp1[i][n]);  
  41.        }  
  42.        printf("%d\n",ans1);  
  43.        printf("%d\n",ans);  
  44.    }  
  45.    return 0;  
  46. }  
#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]

代码:

  1. #include <cstdio>   
  2. #include <cstring>   
  3. using namespace std;  
  4. #define min(a,b) (a) < (b) ? (a) : (b)   
  5. int dp[310][31];  
  6. int sum[310][310];  
  7. int V,P;  
  8. int pos[310];  
  9. int main(){  
  10.    while(scanf("%d%d",&V,&P) != EOF){  //少写了一个%d直接造成结果不对,晕死   
  11.        for(int i = 1; i <= V; ++i)scanf("%d",&pos[i]);  
  12.        memset(sum,0,sizeof(sum));  
  13.        for(int i=1 ; i< V ; i++){  
  14.        for(int j=i+1 ; j<= V ; j++){  
  15.            sum[i][j] = sum[i][j-1]+ pos[j]  -  pos[(i+j) / 2];  
  16.        }  
  17.        }  
  18.        for(int i = 1; i <= V; ++i){  
  19.            dp[i][i] = 0;  
  20.            dp[i][1] = sum[1][i];  
  21.        }  
  22.        for(int j = 2; j <= P; ++j){ //注意为什么把它放在外层   
  23.             for(int i = j+1; i <= V; ++i){  
  24.    
  25.                 dp[i][j] = 9999999;  
  26.                 for(int k = j-1; k < i; ++k)  
  27.                     dp[i][j] =min(dp[i][j],dp[k][j-1]+sum[k+1][i]);  
  28.            }  
  29.        }  
  30.    
  31.        printf("%d\n",dp[V][P]);  
  32.    }  
  33.    return 0;  
  34. }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值