zoukankan      html  css  js  c++  java
  • 洛谷专题-多维DP

    P1004方格取数(四维DP模板题)

    题意:

    一个矩阵,A从左上角走到右下角,B从右下角走到最下角问AB走过的路径和最大是多少,一个点只能被计算一次

    思路:

    定义dp[i][j][k][l]为A走到(i , j) B走到 (k , l) 的最大值

    那么状态转移方程就为dp[i][j][k][l]=max( dp[i1][j][k1][l, dp[i1][j][k][l1, dp[i][j1][k1][l], dp[i][j1][k][l1]a[i][ja[k][l]

    还需注意的是当 i==k && j==l时要减去一次a[i][j]的值,因此重复走一个点只能算一次

    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<cstdio>
     using namespace std;
     const int maxn=10;
     int a[maxn][maxn],dp[maxn][maxn][maxn][maxn];
     int main()
     {
         int n,x,y,c;
         scanf("%d",&n);
         while(scanf("%d%d%d",&x,&y,&c)&&x&&y&&c)
             a[x][y]=c;
         memset(dp,0,sizeof(0));
         for(int i=1;i<=n;i++){
             for(int j=1;j<=n;j++){
                 for(int k=1;k<=n;k++){
                     for(int l=1;l<=n;l++){
                         dp[i][j][k][l]=max(dp[i-1][j][k-1][l],max(dp[i][j-1][k-1][l],max(dp[i-1][j][k][l-1],
                         dp[i][j-1][k][l-1])))+a[i][j]+a[k][l];
                         if(i==k&&j==l) dp[i][j][k][l]-=a[i][j];
                     }
                 }
             }
         }
        cout<<dp[n][n][n][n]<<endl;
      } 
    View Code

    当然还可以降到三维dp[i][j][k]表示走了i步,A走到j行,B走到k行

    那么转移方程式为max(dp[i-1][j][k] , dp[i-1][j-1][k-1] , dp[i-1][j-1][k] , dp[i-1][j][k-1])

    仍要记得去重

    #include<stdio.h>
    using namespace std;
    int n;int map[9][9];
    int d[18][9][9];int res;           
    int max(int a,int b,int c,int d)
    {
     return (((((a>b)?a:b)>c)?((a>b)?a:b):c)>d)?((((a>b)?a:b)>c)?((a>b)?a:b):c):d;
    }
     int main()
     {
        scanf("%d",&n);
        while (1)
        {
            int x;int y;int val;
            scanf("%d%d%d",&x,&y,&val);
            if(x==0&&y==0&&val==0)break;
            map[x-1][y-1]=val;
       }
       d[0][0][0]=map[0][0];
       for(int i=0;i<2*n;i++){
            for(int j=0;j<=i&&j<n;j++){
                for(int k=0;k<=i&&k<n;k++){
                    if(i==0&&j==0&&k==0)continue;
                    d[i][j][k]=map[j][i-j]+map[k][i-k]+
                               max(d[i-1][j][k],d[i-1][j-1][k-1],d[i-1][j-1][k],d[i-1][j][k-1]);
                    if(j==k)d[i][j][k]-=map[j][i-j];         
                }
            }
        }
        res=d[2*(n-1)][n-1][n-1];
        printf("%d",res);
        return 0;
    }
    View Code

     P1006传纸条

    思路:

    与上一题比较类似,就不赘述了

    #include<stdio.h>
    using namespace std;
    int map[55][55];
    int d[120][55][55];int res,n,m;           
    int max(int a,int b,int c,int d)
    {
     return (((((a>b)?a:b)>c)?((a>b)?a:b):c)>d)?((((a>b)?a:b)>c)?((a>b)?a:b):c):d;
    }
     int main()
     {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                scanf("%d",&map[i][j]);
        d[1][1][1]=map[1][1];
        for(int i=2;i<=n+m-1;i++){
            for(int j=1;j<=i&&j<=n;j++){
                for(int k=1;k<=i&&k<=n;k++){
                    if(k==1&&j==1) continue;
                    d[i][j][k]=map[j][i-j+1]+map[k][i-k+1]+
                               max(d[i-1][j][k],d[i-1][j-1][k-1],d[i-1][j-1][k],d[i-1][j][k-1]);
                    if(j==k)d[i][j][k]-=map[j][i-j+1];         
                }
            }
        }
        res=d[n+m-1][n][n];
        printf("%d
    ",res);
        return 0;
    }
    View Code

     P1387最大正方形

    题意:

    给一个01矩阵,问最大的全1子矩阵边长为多少

    思路:

    先看比较暴力的做法,定义dp[i][j]为右下角为(i,j)的全1矩阵边长最大值

    所以我们枚举i,j再看看枚举边长(边长是肯定小于等于dp[i-1][j-1]+1)然后遍历矩阵来判断

    遍历矩阵这个过程我们可以用前缀和进行优化将复杂度降至O(N3)

    做完之后开了大佬的题解。。。。O(N2)即可

    转移方程为f[i][j]=min(min( f[i][j - 1] , f[i-1][j] ), f[i-1][j-1]) + 1

    代码是我的做法,略微复杂

    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<cstdio>
     using namespace std;
     const int maxn=1e2+10;
     int sum1[maxn][maxn],sum2[maxn][maxn];//sum1为i行j列前缀和 
     int dp[maxn][maxn],a[maxn][maxn];
     int n,m;
     void get_sum1()//列前缀和 
     {
         memset(sum1,0,sizeof(sum1));
         for(int i=1;i<=m;i++){//枚举列 
             for(int j=1;j<=n;j++){
                 if(a[j][i]==1) sum1[j][i]=sum1[j-1][i]+1;
                else sum1[j][i]=sum1[j-1][i]; 
             }
         }
     }
     void get_sum2()//列前缀和 
     {
         memset(sum2,0,sizeof(sum2));
         for(int i=1;i<=n;i++){//枚举行 
             for(int j=1;j<=m;j++){
                 if(a[i][j]==1) sum2[i][j]=sum2[i][j-1]+1;
                else sum2[i][j]=sum2[i][j-1]; 
             }
         }
     }
     int main()
     {
         int ans=0;
         scanf("%d%d",&n,&m);
         for(int i=1;i<=n;i++)
             for(int j=1;j<=m;j++){
                 scanf("%d",&a[i][j]);
                 if(i==1||j==1) dp[i][j]=1,ans=1;
             }    
         get_sum1();
         get_sum2();
         memset(dp,0,sizeof(dp));
         for(int i=2;i<=n;i++){
             for(int j=2;j<=m;j++){
                 if(a[i][j]==0) continue;
                 int cnt=dp[i-1][j-1];
                 for(int k=1;k<=cnt+1;k++){
                     if(sum1[i][j]-sum1[i-k][j]==k){
                         if(sum2[i][j]-sum2[i][j-k]==k)
                             dp[i][j]=k;
                     }    
                 }
                ans=max(ans,dp[i][j]);
             }
         }
        cout<<ans<<endl;
        return 0;
      } 
    View Code

     P1417烹调方案(泛化类型的背包)

    题意:

    一共有n件食材,每件食材有三个属性,ai,bi和ci,如果在t时刻完成第i样食材则得到ai-t*bi的美味指数,用第i件食材做饭要花去ci的时间,问在t秒内可以获得的最大美味指数是多少

    思路:

    这题是一道"泛化物品的背包"问题

    也就是说,这种背包,没有固定的费用和价值。这时,将固定的价值换成函数的引用即可

    现在考虑相邻的两个物品x,y。假设现在已经耗费p的时间,那么分别列出先做x,y的代价:

    a[x]-(p+c[x])*b[x]+a[y]-(p+c[x]+c[y])*b[y] (①)

    a[y]-(p+c[y])*b[y]+a[x]-(p+c[y]+c[x])*b[x] (②)

    对这两个式子化简,得到①>②的条件是c[x]*b[y]<c[y]*b[x].

    发现只要满足这个条件的物品对(x,y),x在y前的代价永远更优。

    因此可以根据这个条件进行排序,之后就是简单的01背包了

    #include<iostream>
    #include<algorithm>
    #include<cstdio>
     using namespace std;
     typedef long long ll;
     const int maxn=1e5+10;
     ll dp[maxn];
     struct node{
         ll a,b,c;
     }x[maxn];
     int cmp(node a,node b){return a.b*b.c>b.b*a.c;}
     int main()
     {
         ll t,n;
         scanf("%lld%lld",&t,&n);
         for(int i=1;i<=n;i++) scanf("%lld",&x[i].a);
         for(int i=1;i<=n;i++) scanf("%lld",&x[i].b);
         for(int i=1;i<=n;i++) scanf("%lld",&x[i].c);
         sort(x+1,x+1+n,cmp);
         ll ans=0;
         for(int i=1;i<=n;i++){
             for(int j=t;j>=x[i].c;j--)
                 dp[j]=max(dp[j],dp[j-x[i].c]+x[i].a-j*x[i].b);         
         }
        for(int i=1;i<=t;i++) ans=max(ans,dp[i]);
        cout<<ans<<endl;
     }
    View Code

     P1508 Likecloud-吃、吃、吃

    思路:

    水题,状态方程式f[i][j]=max(max(f[i-1][j],f[i-1][j-1]),f[i-1][j+1])+a[i][j]

    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<cstdio>
    #define inf 0x3f3f3f3f
     using namespace std;
     const int maxn=600;
     int dp[maxn][maxn],a[maxn][maxn];
     int main()
     {
         int n,m;
        memset(a,-inf,sizeof(a));
         scanf("%d%d",&n,&m);
         int y=m/2+1;
         for(int i=1;i<=n;i++)
             for(int j=1;j<=m;j++)
                 scanf("%d",&a[i][j]);    
         for(int i=1;i<=n;i++)
             for(int j=1;j<=m;j++)
                 dp[i][j]=max(dp[i][j],max(dp[i-1][j-1],max(dp[i-1][j],dp[i-1][j+1])))+a[i][j]; 
        cout<<max(max(dp[n][y],dp[n][y-1]),dp[n][y+1])<<endl;
        return 0;
     }
    View Code

     P1855 榨取kkksc03(多维01背包)

    题意:

    有n个任务,完成每个任务有对应的时间与花费,现在你有m元与时间t请问你最多能完成多少任务

    思路:

    多维01背包,滚动数组的状态转移式为:dp[j][k]=max(dp[j][k],dp[j-a[i].x][k-a[i].y]+1)

    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<cstdio>
     using namespace std;
     const int maxn=205;
     int dp[maxn][maxn];
     struct node{
         int x,y;
     }a[maxn];
      int main()
      {
          int n,m,t;
          scanf("%d%d%d",&n,&m,&t);
          for(int i=1;i<=n;i++) scanf("%d%d",&a[i].x,&a[i].y);
          int ans=0;
          for(int i=1;i<=n;i++){
              for(int j=m;j>=a[i].x;j--){
                  for(int k=t;k>=a[i].y;k--){
                      dp[j][k]=max(dp[j][k],dp[j-a[i].x][k-a[i].y]+1);
                      ans=max(dp[j][k],ans);
                  }
              }
          }
        cout<<ans<<endl;
        return 0;
      }
    View Code

    P1736 创意吃鱼法

    题意:

    给一个01矩阵,问最大的只有对角线上为1的子矩阵边长是多少

    思路:

    定义dp[i][j]为点a[i][j]作为左下角或右下角的符合要求的子矩阵边长的最大值

    假设x为a[i][j]上方连续0的个数,y为左方连续0的个数,z为右方连续0的个数

    那么转移方程就为:

    dp[i][j]=max(dp[i][j] , min( dp[i-1][j-1] , min(x,y) ) +1)
    dp[i][j]=max(dp[i][j] , min( dp[i-1][j+1], min(x,z) ) +1)

    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<vector>
    #include<cstdio>
    #define inf 0x3f3f3f3f
     using namespace std;
     typedef long long ll;
     const int maxn=2510;
     int dp[maxn][maxn],a[maxn][maxn];
     int main()
     {
         int n,m;
         scanf("%d%d",&n,&m);
         for(int i=1;i<=n;i++)
             for(int j=1;j<=m;j++)
                 scanf("%d",&a[i][j]);
         int ans=0;
         for(int i=1;i<=n;i++){
             for(int j=1;j<=m;j++){
                 if(a[i][j]==0)    continue;
                 int x=0,y=0,z=0;
                 for(int k=i-1;k>0&&a[k][j]==0;k--,x++);
                 for(int k=j-1;k>0&&a[i][k]==0;k--,y++);
                 for(int k=j+1;k<=m&&a[i][k]==0;k++,z++);
                 dp[i][j]=max(dp[i][j],min(dp[i-1][j-1],min(x,y))+1);
                 dp[i][j]=max(dp[i][j],min(dp[i-1][j+1],min(x,z))+1);
                 ans=max(ans,dp[i][j]);
             }
         }
        cout<<ans<<endl;
         return 0;
     }
    View Code
  • 相关阅读:
    IOS -- 获取本地图片和网络图片的大小size
    xib中的label加边框
    iOS开发之Masonry框架源码深度解析
    10分钟搭建 App 主流框架
    卸载服务器GitLab
    linux安装git方法
    虚拟机安装centos7, 再安装gitlab 简单步骤
    collectionView 防止cell复用的方法
    UIButton 设置图片文字垂直居中排列
    button获取验证码60秒倒计时 直接用
  • 原文地址:https://www.cnblogs.com/overrate-wsj/p/12335362.html
Copyright © 2011-2022 走看看