zoukankan      html  css  js  c++  java
  • 【学习笔记】dp基础

    知识储备:dp入门

    好了,完成了dp入门,我们可以做一些稍微不是那么裸的题了。

    dp基础,主要是做题,只有练习才能彻底掌握。


    洛谷P1417 烹调方案

    分析:由于时间的先后会对结果有影响,所以c[i]*b[j]>c[j]*b[i]为条件排序,然后再01背包。

    洛谷P1387 最大正方形

    分析:用dp[i][j]来表示以a[i][j](正方形数组)为右下角最后一个数的正方形边长,a数组可以直接用dp数组代替掉。

     1 #include <cstdio>
     2 #include <algorithm>
     3 using namespace std;
     4 const int maxn=103;
     5 int dp[maxn][maxn];
     6 int min_a(int a,int b,int c)
     7 {
     8     return min(min(a,b),min(b,c));
     9 }
    10 int main()
    11 {
    12     int n,m,ans=-1;
    13     scanf("%d%d",&n,&m);
    14     for(int i=1;i<=n;i++)
    15         for(int j=1;j<=m;j++)
    16         scanf("%d",&dp[i][j]);
    17     for(int i=1;i<=n;i++)
    18     {
    19         for(int j=2;j<=m;j++)
    20         {
    21             if(dp[i][j]>=1)
    22             {
    23                 dp[i][j]=min_a(dp[i-1][j],dp[i-1][j-1],dp[i][j-1])+1;
    24             }
    25         }
    26     }
    27     for(int i=1;i<=n;i++)
    28     {
    29         for(int j=1;j<=m;j++)
    30         ans=max(ans,dp[i][j]);
    31     }
    32     printf("%d",ans);
    33     return 0;
    34 }
    View Code

    洛谷P1006 传纸条

    分析:拿到题我不假思索地写了一份 单次最大好心值+dfs路径还原标记+单次最大好心值,然而发现,第一次最大好心值的路线把第二次堵死了。。。

    好吧,正解是双线程,dp[k][i][j]表示走了k步,两份纸条横坐标分别是i,j时的好心最大值。

    安利一发别人博客,讲得很透彻:http://www.cnblogs.com/OIerShawnZhou/p/7492555.html

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <algorithm>
     4 using namespace std;
     5 const int maxn=60;
     6 int dp[2*maxn][maxn][maxn],a[maxn][maxn],go[maxn][maxn];
     7     //dp[k][i][j]走了k步,两份纸条横坐标分别是i,j时的好心最大值
     8 int max_a(int a,int b,int c,int d)
     9 {
    10     return max(max(a,b),max(c,d));
    11 } 
    12 int main()
    13 {
    14     int m,n,ans=0;
    15     scanf("%d%d",&m,&n);
    16     for(int i=1;i<=m;i++)
    17     for(int j=1;j<=n;j++)
    18     scanf("%d",&a[i][j]);
    19     for(int k=1;k<n+m;k++)//步数 
    20     {
    21         for(int i=1;i<=k;i++)
    22         {
    23             for(int j=1;j<=k;j++)
    24             {
    25                 dp[k][i][j]=max_a(dp[k-1][i][j],dp[k-1][i][j-1],dp[k-1][i-1][j],dp[k-1][i-1][j-1])+a[i][k-i+1]+a[j][k-j+1];
    26                 if(i==j) dp[k][i][j]-=a[i][k-i+1];//重复了 
    27             }
    28         }
    29     } 
    30     printf("%d",dp[n+m-1][m][m]);
    31     return 0;
    32 }
    View Code

    洛谷P1282 多米诺骨牌

    分析:拿到题有点懵,但仔细想想,这好像也算是01背包,可以把翻转每个骨牌得到的点数差看做一件物品(记住,点数不带绝对值),但会出现数组下标为负数的情况,这个时候把 N 看做为0,N-1就是-1,N+1就是1

      dp[i][j]:前i张牌使得上一行点数之和为j的最小方法数

     普通版:

     1 #include <cstdio>
     2 #include <cmath>
     3 #include <algorithm>
     4 using namespace std;
     5 const int maxn=1005;
     6 const int N=maxn*5,INF=9999999;
     7 int dp[maxn][2*N],a[maxn],b[maxn];//dp[i][j]:前i张牌使得上一行点数之和为j的最小方法数
     8 int main()
     9 {
    10     int n,minnum=INF,k=0;
    11     scanf("%d",&n);
    12     for(int i=1;i<=n;i++)
    13     {
    14         scanf("%d%d",&a[i],&b[i]);    
    15     }
    16     for(int i=0;i<maxn;i++)
    17     for(int j=0;j<2*N;j++)
    18         dp[i][j]=INF;
    19     dp[0][N]=0;
    20     for(int i=1;i<=n;i++)
    21     {
    22         for(int j=-N;j<=N;j++)
    23         {
    24             int dis=a[i]-b[i];//有正有负,故而开数组要开2*N,下面j+N同理
    25             dp[i][j+N]=min(dp[i-1][j+dis+N]+1,dp[i-1][j-dis+N]);//翻或不翻 
    26         }
    27     }
    28      for(int i=0;i<=N;i++)
    29     {
    30         minnum=min(dp[n][N-i],dp[n][N+i]);
    31         if(minnum!=INF)
    32         {
    33             printf("%d",minnum);
    34             return 0;
    35         }
    36     }
    37     return 0;
    38 }
    View Code

     滚动数组优化:

     1 #include <cstdio>
     2 #include <cmath>
     3 #include <algorithm>
     4 using namespace std;
     5 const int maxn=1005;
     6 const int N=maxn*10,INF=9999999;
     7 int dp[2][2*N],a[maxn],b[maxn];//dp[i][j]:前i张牌使得上一行点数之和为j的最小方法数
     8 int main()
     9 {
    10     int n,minnum=INF,k=0;
    11     scanf("%d",&n);
    12     for(int i=1;i<=n;i++)
    13     {
    14         scanf("%d%d",&a[i],&b[i]);    
    15     }
    16     for(int i=0;i<2;i++)
    17     for(int j=0;j<2*N;j++)
    18         dp[i][j]=INF;
    19     dp[0][N]=0;
    20     for(int i=1;i<=n;i++)
    21     {
    22         for(int j=-N;j<=N;j++)
    23         {
    24             int dis=a[i]-b[i];//有正有负,故而开数组要开2*N,下面j+N同理
    25             dp[i&1][j+N]=min(dp[(i-1)&1][j+dis+N]+1,dp[(i-1)&1][j-dis+N]);//翻或不翻 
    26         }
    27     }
    28      for(int i=0;i<=N;i++)
    29     {
    30         minnum=min(dp[n&1][N-i],dp[n&1][N+i]);
    31         if(minnum!=INF)
    32         {
    33             printf("%d",minnum);
    34             return 0;
    35         }
    36     }
    37     return 0;
    38 }
    View Code

    洛谷P1880 石子合并

    分析:曾经的noi系列。石子合并问题分好几种情况。

    这题是环形的,且只能合并相邻的两堆,那么我们把石子数组展开,如[1,2,3,4,5] -> [1,2,3,4,5,1,2,3,4,5],这样进行处理,就能将环形转换成直线。

    转换完之后,就可以用dp做了,dp的解释详见代码。这题数据过得去,时间是 O(n3) 不然要用平行四边形优化。

    安利一发acdreamer的博客,把石子合并问题讲的很清楚http://blog.csdn.net/acdreamers/article/details/18039073

     1 #include <cstdio>
     2 #include <algorithm>
     3 using namespace std;
     4 int sum[205],mins[205][205],maxs[205][205],s[205][205];//dp[i][j]=i~j的最大得分
     5 int main()
     6 {
     7     int  a,n,minnum=9999999,maxnum=-minnum;
     8     scanf("%d",&n);
     9     for(int i=1;i<=n;i++)
    10     {
    11         scanf("%d",&sum[i]);
    12         sum[i+n]=sum[i];
    13         s[i][i]=i;
    14         s[i*2][i*2]=i;
    15     }
    16     for(int i=1;i<=2*n;i++) sum[i]+=sum[i-1]; //前缀和 
    17     for(int l=1;l<=n;l++)//区间长度
    18     {
    19         for(int i=1;i+l<=2*n;i++)//开始的端点 
    20         {
    21             
    22             int j=i+l;//结束的端点
    23             maxs[i][j]=-99999999;mins[i][j]=-1*maxs[i][j];
    24             for(int k=i;k<j;k++)
    25             {
    26                 maxs[i][j]=max(maxs[i][j],maxs[i][k]+maxs[k+1][j]+sum[j]-sum[i-1]);
    27                 mins[i][j]=min(mins[i][j],mins[i][k]+mins[k+1][j]+sum[j]-sum[i-1]);
    28             }
    29         }
    30     } 
    31     for(int i=1;i<=n;i++)//因为是环形的,可从任意两堆间分开,所以需要枚举一遍起点从1~n 
    32     {
    33         maxnum=max(maxs[i][i+n-1],maxnum);
    34         minnum=min(mins[i][i+n-1],minnum);
    35     }
    36     printf("%d
    %d",minnum,maxnum);
    37     return 0;
    38 } 
    View Code

    洛谷P1108 低价购买

    分析:第一问还是很简单的,求最长下降子序列。第二问要计算方案数量,还要判重。

    用t[i]表示前i个股票的不同方案个数,可以得出,如果存在dp[j]==dp[i] && a[j]==a[i]方案就是重复的,所以就把t[i]赋为0,如果f[i]==f[j]+1,那么i可以从j转移,所以t[i]+=t[j]。最后统计和即可。

     1 #include <cstdio>
     2 #include <algorithm>
     3 using namespace std;
     4 int dp[5005],a[5005],t[5005];//t,计算出现次数的dp 
     5 int main()
     6 {
     7     int ans=-99999,n,k=0,res=0;
     8     scanf("%d",&n);
     9     for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    10     for(int i=1;i<=n;i++)
    11     {
    12         dp[i]=1;
    13         for(int j=1;j<i;j++)
    14         {
    15             if(a[j]>a[i]) dp[i]=max(dp[i],dp[j]+1);
    16         }
    17         ans=max(ans,dp[i]);
    18     }
    19     for(int i=1;i<=n;i++)
    20     {
    21         if(dp[i]==1) t[i]=1;//如果无法转移则为1 
    22         for(int j=1;j<i;j++)
    23         {
    24             if(dp[j]==dp[i]-1 && a[i]<a[j])//判前继 判可以为下一个数 
    25             {
    26                 t[i]+=t[j];//转移
    27             }
    28             else if(dp[i]==dp[j]&&a[i]==a[j]) t[j]=0;//判重
    29         }
    30     }
    31     for(int i=1;i<=n;i++)
    32     {
    33         if(dp[i]==ans) res+=t[i];
    34     }
    35     printf("%d %d",ans,res);
    36     return 0;
    37 }
    View Code
  • 相关阅读:
    22 element-ui之Form表单el-form标签
    21 Vue的section组件
    21 Vue2.0 的transition组件
    Spring注解之组件注册
    Mybatis自动提交失败:Setting autocommit to false on JDBC Connection
    用STM32定时器测量信号频率——测频法和测周法[原创cnblogs.com/helesheng]
    mac ssh 总是自动断开
    cmake 简易教程
    mac 安装 mongodb
    新手程序员一般有如下特点
  • 原文地址:https://www.cnblogs.com/noblex/p/7627129.html
Copyright © 2011-2022 走看看