复习/学习DP的长记

5 篇文章 0 订阅
3 篇文章 0 订阅

01背包问题
采药
二维

#include<cstdio>
#include<iostream> 
#include<cstring>
using namespace std;
int t,m;
const int maxn=1005;
int v[maxn],tm[maxn],dp[maxn][maxn];
int main()
{
    memset(v,0,sizeof(v));
    memset(tm,0,sizeof(tm));
    memset(dp,0,sizeof(dp));
    scanf("%d%d",&t,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&tm[i],&v[i]);
    }
    for(int i=1;i<=m;i++)
    for(int j=0;j<=t;j++)
    {
        if(j-tm[i]>=0) dp[i][j]=max(dp[i-1][j-tm[i]]+v[i],dp[i-1][j]);
        else dp[i][j]=dp[i-1][j];
    }
    printf("%d\n",dp[m][t]);
    return 0; 
}
//若题目要求:恰好装满容积为V的背包的最大价值:初始化dp[0][i](i!=0)=-INF
//保证所有的状态都是从起点dp[0][0]转移得到的 每次装物品时,必定从已经装了体积为j-ci的物品的背包转移过来。 

一维

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=1005;
int t,m;
int v[maxn],tm[maxn],dp[maxn];
int main()
{
    scanf("%d%d",&t,&m);
    for(int i=1;i<=m;i++) scanf("%d%d",&tm[i],&v[i]);

    for(int i=1;i<=m;i++)
    for(int j=t;j>=tm[i];j--)
    {
        dp[j]=max(dp[j],dp[j-tm[i]]+v[i]);
    }
    printf("%d\n",dp[t]);
    return 0;
}

金明的预算方案

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int N,m,num;
int sfv[300][3],sfw[300][3],dp[40000+5];
int main()
{
    scanf("%d%d",&N,&m);
    for(int i=1;i<=m;i++)
    {
        int v,w;
        scanf("%d%d%d",&v,&w,&num);
        if(!num)
        {
            sfv[i][0]=v;
            sfw[i][0]=w;
        }
        else 
        {
            if(!sfv[num][1])
            {
                sfv[num][1]=v;
                sfw[num][1]=w;                      
            }
            else 
            {
                sfv[num][2]=v;
                sfw[num][2]=w;
            }
        }
    }
    for(int i=1;i<=m;i++)
    for(int j=N;j>=sfv[i][0];j--)
    {
        dp[j]=max(dp[j-sfv[i][0]]+sfv[i][0]*sfw[i][0],dp[j]);
        if(j>=sfv[i][0]+sfv[i][1])
        dp[j]=max(dp[j-sfv[i][0]-sfv[i][1]]
                  +sfv[i][0]*sfw[i][0]
                  +sfv[i][1]*sfw[i][1]
                  ,dp[j]);
        if(j>=sfv[i][0]+sfv[i][2])
        dp[j]=max(dp[j-sfv[i][0]-sfv[i][2]]
                  +sfv[i][0]*sfw[i][0]
                  +sfv[i][2]*sfw[i][2]
                  ,dp[j]);
        if(j>=sfv[i][0]+sfv[i][1]+sfv[i][2])
        dp[j]=max(dp[j-sfv[i][0]-sfv[i][1]-sfv[i][2]]
                  +sfv[i][0]*sfw[i][0]
                  +sfv[i][1]*sfw[i][1]
                  +sfv[i][2]*sfw[i][2]
                  ,dp[j]);
    }
    printf("%d\n",dp[N]);
}

最长上升子序列
拦截导弹
O(n^2)

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=30000+5;
int n;
int a[maxn],dp[maxn],dp1[maxn];
int main()
{
    int n=0;
    while(scanf("%d",&a[++n])>=1);n--;
    for(int i=1;i<=n;i++) dp[i]=dp1[i]=1;
    int ans=0,ans1=0;
    for(int i=1;i<=n;i++)
    for(int j=1;j<i;j++)
    {
        if(a[i]<=a[j])
        dp[i]=max(dp[i],dp[j]+1);
        ans=max(ans,dp[i]);
        if(a[i]>a[j])
        dp1[i]=max(dp1[i],dp1[j]+1);
        ans1=max(ans1,dp1[i]);
    }
    printf("%d\n%d\n",ans,ans1);

} 

合唱队形

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=30000+5;
int n;
int a[maxn],dp[maxn],dp1[maxn];
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++) dp[i]=dp1[i]=1;
    int ans1=0;
    for(int i=1;i<=n;i++)
    for(int j=1;j<i;j++)
    {
        if(a[i]>a[j])
        dp[i]=max(dp[i],dp[j]+1);
    }
    for(int i=n;i>=1;i--)
    for(int j=i+1;j<=n;j++)
    {
        if(a[i]>a[j])
        dp1[i]=max(dp1[i],dp1[j]+1);
    }
    for(int k=1;k<=n;k++)
    {
        ans1=max(dp[k]+dp1[k]-1,ans1);
    } 
    printf("%d\n",n-ans1);

} 

方格取数问题
方格取数

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=11;
int mp[maxn][maxn],dp[maxn][maxn][maxn][maxn];
int n;
int main()
{
    scanf("%d",&n);
    int x,y,v;
    memset(mp,0,sizeof(mp));
    memset(dp,0,sizeof(dp));
    while(scanf("%d%d%d",&x,&y,&v)>=3)
    {
        if(x==0&&y==0&&v==0) break;
        else 
        {
            mp[x][y]=v;
        }
    }
    int sum;
    for(int i=1;i<=n;i++)
      for(int j=1;j<=n;j++)
      {
        sum=i+j;
        for(int i1=1,j1=sum-i1;i1<=n&&i1+1<=sum;i1++,j1=sum-i1)
        {
                dp[i][j][i1][j1]=mp[i][j]+mp[i1][j1]+max(max(dp[i-1][j][i1-1][j1],
                                                             dp[i][j-1][i1-1][j1]),
                                                         max(dp[i-1][j][i1][j1-1],
                                                             dp[i][j-1][i1][j1-1]));
                if(i==i1&&j==j1) dp[i][j][i1][j1]-=mp[i][j];
        }
      }
    cout<<dp[n][n][n][n]<<'\n';
    return 0;
}

传纸条
蒟蒻的丑陋代码:

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=55;
int mp[maxn][maxn],dp[maxn][maxn][maxn][maxn];
int m,n;
int main()
{
    scanf("%d%d",&m,&n);
    memset(mp,0,sizeof(mp));
    memset(dp,0,sizeof(dp));
    for(int i=1;i<=m;i++)
    for(int j=1;j<=n;j++)
      scanf("%d",&mp[i][j]);
    int sum;
    for(int i=1;i<=m;i++)
      for(int j=1;j<=n;j++)
      {
          sum=i+j;
          int i1=1;
        for(int j1=sum-i1;i1+1<=sum;i1++,j1=sum-i1)
        {
            if(i1 > m) continue;//************
            if(j1 > n) continue;//************
                dp[i][j][i1][j1]=mp[i][j]+mp[i1][j1]+max(max(dp[i-1][j][i1-1][j1],
                                                             dp[i][j-1][i1-1][j1]),
                                                         max(dp[i-1][j][i1][j1-1],
                                                             dp[i][j-1][i1][j1-1]));
                if(i==i1&&j==j1) dp[i][j][i1][j1]-=mp[i][j];
        }
      }
    cout<<dp[m][n][m][n]<<'\n';
    return 0;
}

wzhd大佬的优化版:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int dp[55][55][55][55],map[55][55];
int MAX(int a,int b,int c,int d)
{
    return max(a,max(b,max(c,d)));
}
int main()
{
    int m,n;
    scanf("%d%d",&m,&n);
    for(int i = 1;i <= m;i ++)
        for(int j = 1;j <= n;j ++)
            scanf("%d",&map[i][j]);
    for(int i = 1;i <= m;i ++)
        for(int j = 1;j < n;j ++)
            for(int k = 1;k <= m;k ++)
                for(int l = j + 1;l <= n;l ++)
                    dp[i][j][k][l] = MAX(dp[i - 1][j][k - 1][l],dp[i - 1][j][k][l - 1],dp[i][j - 1][k][l - 1],dp[i][j - 1][k - 1][l]) + map[i][j] + map[k][l];
    printf("%d",dp[m][n - 1][m - 1][n]);
}

最长公共子序列问题
最长公共子序列

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int n;
const int maxn=3000+5;
long a[maxn],b[maxn],dp[maxn][maxn];
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++) scanf("%d",&b[i]);
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
    {
        if(a[i]==b[j]) dp[i][j]=max(dp[i-1][j-1]+1,
                                    max(dp[i-1][j],dp[i][j-1]));
        else dp[i][j]=max(dp[i-1][j-1],
                          max(dp[i-1][j],dp[i][j-1]));
    }
    printf("%ld\n",dp[n][n]);
    return 0;
}

买帽子

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int tot=0,n,dp[505][505];
string s,ss;
char s1[505];
struct Hat
{
    string a;
    int l;
}hat[505];
bool cmp(Hat A,Hat B)
{
    if(A.l==B.l) return A.a<B.a;
    else return A.l>B.l;
}
int main()
{
    scanf("%d",&n);
    while(n--)
    {
        s=" ";s1[0]=' ';
        cin>>ss;s+=ss;
        int len=s.length();
        for(int j=1;j<s.length();j++) s1[j]=s[len-j];//exchange

        for(int i=1;i<len;i++)
             dp[i][len-i]=1;
        for(int i=1;i<len;i++)
         for(int j=1;j<len;j++)
         {
            if(s[i]==s1[j])
            {
                dp[i][j]=max(dp[i-1][j-1]+1,
                                    max(dp[i-1][j],dp[i][j-1]));
            }
            else dp[i][j]=max(dp[i-1][j-1],
                          max(dp[i-1][j],dp[i][j-1]));
         }
        hat[++tot]=(Hat){s,dp[len-1][len-1]};
    }
    sort(hat+1,hat+1+tot,cmp);
    for(int i=1;i<=tot;i++) 
    {
        for(int j=1;j<hat[i].a.length();j++) 
          cout<<hat[i].a[j];
        printf("\n");
    }

    return 0;
} 

编辑距离问题

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
string aa,bb;
int dp[4005][4005];
int main()
{
    cin>>aa>>bb;
    string a=" ",b=" ";
    a+=aa,b+=bb;
    for(int i=1;i<a.length();i++) dp[i][0]=i;//delete
    for(int j=1;j<b.length();j++) dp[0][j]=j;//add
    for(int i=1;i<a.length();i++)
    for(int j=1;j<b.length();j++)
    {
        if(a[i]!=b[j])
        {
            dp[i][j]=min(dp[i-1][j-1]+1,min(dp[i][j-1]+1,dp[i-1][j]+1));
        }
        else dp[i][j]=dp[i-1][j-1];
    }
    printf("%d",dp[a.length()-1][b.length()-1]);
}

区间DP
石子归并

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int n;
const int maxn=105;
int a[maxn],dp[maxn][maxn],qzh[maxn];
int main() 
{
    memset(dp,0x3f3f3f3f,sizeof(dp));
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        dp[i][i]=0;//长度为0的区间 
        qzh[i]=qzh[i-1]+a[i];
    } 
    for(int i=n;i>=1;i--)
      for(int j=i+1;j<=n;j++)
        for(int k=i;k<j;k++)//注意不是k=i+1:保证长度为1的区间和为前缀和 
        {
            dp[i][j]=min(dp[i][k]+dp[k+1][j]+qzh[j]-qzh[i-1],dp[i][j]); 
        }
    printf("%d",dp[1][n]);
    return 0;
}

能量项链

//考试的时候做出来的,开心QwQ
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
int n;
int a[400+5];
int power[400+5][400+5];
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) 
    {
        scanf("%d",&a[i]);
        a[i+n]=a[i];
    }
    a[2*n+1]=a[1];
    for(int i=2*n;i>=1;i--)
    for(int j=i+1;j<=2*n;j++)
    for(int k=i;k<j;k++)
    {
        power[i][j]=max(power[i][j],power[i][k]+power[k+1][j]+a[i]*a[k+1]*a[j+1]);
    }
    int ans=0;
    for(int i=1;i<=n;i++) ans=max(ans,power[i][i+n-1]);
    printf("%d\n",ans);
    return 0;
}

乘法游戏

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int n;
int a[100+5];
int dp[105][105];
int main()
{
    memset(dp,0x3f3f3f3f,sizeof(dp));
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        dp[i-1][i]=0; 
    }
    for(int i=n-2;i>=1;i--)
      for(int j=i+2;j<=n;j++)
        for(int k=i+1;k<j;k++)
        {
            dp[i][j]=min(dp[i][k]+dp[k][j]+a[i]*a[k]*a[j],dp[i][j]);
        }
    printf("%d\n",dp[1][n]);
    return 0;
}

乘积最大
蒟蒻的5 for

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
int n,k,dp[100][100][10],b[100];
char a[100];
int main()
{
    scanf("%d%d",&n,&k);getchar();
    for(int i=0;i<=n;i++)
    {
        scanf("%c",&a[i]);
        if(i>=1)
        {
            dp[i][i][0]=b[i]=a[i]-'0';
        }
    }
    int la;
    for(int i=n;i>=1;i--)
    {
        la=10;
        for(int j=i+1;j<=n;j++)
        {
          for(int k=i;k<j;k++)
          {
            dp[i][j][0]=dp[i][k][0]*la+dp[k+1][j][0];
          }
        }
    }
    for(int i=n-1;i>=1;i--)
      for(int j=i+1;j<=n;j++)
        for(int k=1;k<=(j-i);k++)
          for(int l=i;l<j;l++)
          {
            for(int m=0;k-1-m>=0;m++)
            dp[i][j][k]=max(dp[i][l][m]*dp[l+1][j][k-1-m],
                            max(dp[i][l][k-1-m]*dp[l+1][j][m],
                            dp[i][j][k]));
          }
    printf("%d\n",dp[1][n][k]);
}

wzhd(dalao)改后的4 for及总结

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
int n,k,dp[100][100][10],b[100];
char a[100];
int main()
{
    scanf("%d%d",&n,&k);getchar();
    for(int i=0;i<=n;i++)
    {
        scanf("%c",&a[i]);
        if(i>=1)
        {
            dp[i][i][0]=b[i]=a[i]-'0'; // 拆成个位 : 第 i 位 
        }
    }
    int la = 10;
    for(int i=n;i>=1;i--) //因为我们每次向后去找他的下一位组合起来,因此倒序。 
        for(int j=i+1;j<=n;j++)//先组成2位数,依次增加。 
            dp[i][j][0]=dp[i][j-1][0]*la+dp[j][j][0];  
    for(int i=n-1;i>=1;i--)
      for(int j=i+1;j<=n;j++)//同上枚举 
        for(int m=1;m<=k;m++)// times of‘*’ 
          for(int l=i;l<=j;l++)//枚举断点 
          {
            //枚举左右两区间各有几个“*”。
              //其实只要固定左边是1其实都会枚举到的 
               //方便叙述 (1 * 2 * 3) * 4 /(1 * 2)* (3 * 4) 显然重了, 
            dp[i][j][m]=max(dp[i][l][m-1]*dp[l+1][j][0],dp[i][j][m]); 
          }
    printf("%d\n",dp[1][n][k]);
}

对于石子归并,因为合并的顺序不同会产生不同的结果。

所以石子归并不能规定最后一个合并的对象。

而是将左边合并的结果与右边合并的结果进行合并。

所以我们合并的时候,需要右边的合并结果。

换句话说,我们需要枚举起点而使得我们可以访问到右边的区间。

而乘积最大中,由于乘法满足交换律,我们可以规定最后一个乘的对象。

假设我们规定右边不进行乘法运算,只是将左边的区间乘右边的区间组成的一个数。

而这个数是预处理好的。

由于我们只是将左边合并的结果与右边这一个数合并。

所以我们需要的条件只是从1~i的合并结果,故不需要枚举起点。

总结,合并区间的时候我们并不能盲目地去合并这个区间,而是先观察合并的顺序对合并的结果是否有影响。是否我们需要处理出每一个区间的结果。

记忆化搜索
滑雪

//开全局变量时要慎重,回溯时可能出错 
#include<cstdio> 
#include<iostream>
#include<cstring>
using namespace std;
int n,m;
const int maxn=1050;
int dp[maxn][maxn],mp[maxn][maxn];
int nx[]={0,0,1,-1};
int ny[]={1,-1,0,0};
int dfs(int x,int y)
{
    if(dp[x][y]) return dp[x][y];
    int ans=1; 
    for(int i=0;i<4;i++)
    {
        int tx=nx[i]+x,ty=ny[i]+y;
        if(tx<=n&&tx>=1&&ty<=m&&ty>=1) 
        {
            if(mp[tx][ty]<mp[x][y])
            {
                ans=max(ans,dfs(tx,ty)+1);
            }
        } 
    }
    dp[x][y]=ans;
    return ans;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {
        scanf("%d",&mp[i][j]);
    }
    int ans1=1;
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
      ans1=max(ans1,dfs(i,j));
    cout<<ans1;
}

方案数
传球游戏

#include<cstdio>
#include<iostream>
using namespace std;
int n,m;
int dp[200][200];
int main()
{
    cin>>n>>m;
    dp[0][1]=1;
    for(int i=1;i<=m;i++)
    for(int j=1;j<=n;j++)
    {
        if(j==1) dp[i][j]=dp[i-1][n]+dp[i-1][2];
        else if(j==n) dp[i][j]=dp[i-1][n-1]+dp[i-1][1];
        else dp[i][j]=dp[i-1][j-1]+dp[i-1][j+1];
    }
    cout<<dp[m][1];
}

摆花
二维

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int n,m;
int a[108],f[108][108];
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
     scanf("%d",&a[i]);
    for(int i=0;i<=a[1];i++) f[1][i]=1;
    for(int i=2;i<=n;i++)
      for(int j=0;j<=m;j++)
        for(int k=0;k<=a[i];k++)
          if(j-k>=0)
            f[i][j]=(f[i][j]+f[i-1][j-k])%1000007;
    printf("%d",f[n][m]);
}

一维

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int n,m;
int a[108],f[108];
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
     scanf("%d",&a[i]);
    f[0]=1;
    for(int i=1;i<=n;i++)
      for(int j=m;j>=1;j--)
        for(int k=1;k<=a[i];k++)//**从1开始的原因:自己想
          if(j-k>=0)
            f[j]=(f[j]+f[j-k])%1000007;
    printf("%d",f[m]);
}

未完待续

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值