【动态规划】传纸条

http://icpc.upc.edu.cn/problem.php?cid=1675&pid=0

 

这一道题的思路很简单,暴力DP都可以过(就是用4个for),但是有可能会爆空间。

先来讲讲暴力DP的思路吧

这一道题可以看成是求一个人从左上角到右下角走两次所经过路线的最大值,所以用两个for来枚举第一次走的横纵坐标,另外两个for来枚举第二次做的横纵坐标,

一共分四种情况:

1、f[i][j][k][l]=f[i-1][j][k-1][l]//两条路都是从上面走下来的

2、f[i][j][k][l]=f[i][j-1][k][l-1]//两条都是从左边走过来的

3、f[i][j][k][l]=f[i-1][j][k][l-1]//一条是从上面走下来的,一条是从左边走过来的

4、f[i][j][k][l]=f[i][j-1][k-1][l]//一条是从左边走过来的,一条是从上面走下来的

可以推出状态转移方程:f[i][j][k][l]=max(max(f[i-1][j][k-1][l],f[i][j-1][k][l-1]),max(f[i-1][j][k][l-1],f[i][j-1][k-1][l]))+a[i][j];

还要加上一条判断语句:if(i!=k&&j!=l) f[i][j][k][l]+=a[k][l];

完整代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
int m,n;
int a[55][55];
ll dp[55][55][55][55];
int main()
{
    scanf("%d%d",&m,&n);
    for(int i=1;i<=m;i++)
    {
        for(int j=1;j<=n;j++)
        {
            scanf("%d",&a[i][j]);
        }
    }
    for(int i=1;i<=m;i++)
    {
        for(int j=1;j<=n;j++)
        {
            for(int k=m;k>=1;k--)
            {
                for(int l=n;l>=1;l--)
                {
                    ll x=max(dp[i-1][j][k-1][l],dp[i][j-1][k][l-1]);
                    ll y=max(dp[i-1][j][k][l-1],dp[i][j-1][k-1][l]);
                    dp[i][j][k][l]=max(x,y)+a[i][j];
                    if(i!=k && j!=l)
                        dp[i][j][k][l]+=a[k][l];
                }
            }
        }
    }
    printf("%lld\n",dp[m][n][m][n]);
    return 0;
}


 

这种方法既浪费空间,又容易超时,所以我们对它进行了时间的优化

因为两个人到目的地的步数相同,所以枚举步数和两个人的横坐标之后,就可以算出两个人纵坐标,

但是状态转移方程就要复杂一点:(因为枚举步数,就要加上每个人的位置的值)

f[k][i][j]=max(max(f[k-1][i][j],f[k-1][i-1][j-1]),max(f[k-1][i-1][j],f[k-1][i][j-1]))+a[i][k+1-i]+a[j][k+1-j];

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
int m,n;
int a[55][55];
ll dp[105][55][55];
int main()
{
    scanf("%d%d",&m,&n);
    for(int i=1; i<=m; i++)
    {
        for(int j=1; j<=n; j++)
        {
            scanf("%d",&a[i][j]);
        }
    }
 
    for(int k=1; k<=m+n-1; k++)
    {
        int m1=min(m,k);
        for(int i=1; i<=m1; i++)
        {
            for(int j=1; j<=m1; j++)
            {
                ll c=a[i][k-i+1]+a[j][k-j+1];
                ll x=max(dp[k-1][i][j],dp[k-1][i-1][j-1]);
                ll y=max(dp[k-1][i-1][j],dp[k-1][i][j-1]);
                ll t=max(x,y)+c;
                dp[k][i][j]=t;
                if(i==j)
                    dp[k][i][j]-=c/2;
            }
        }
    }
    printf("%lld\n",dp[m+n-1][m][m]);
    return 0;
}


 
 

这样,既节省了空间,又能给程序提速。最终空间为100*50*50。


还有一种最优的办法:枚举一个人的横纵坐标和另一个人的横坐标,因为步数相同,就可以算出另一个人的纵坐标

用同样的办法,可以找出状态转移方程。

f[i][j][k]=max(max(f[i-1][j][k-1],f[i-1][j][k]),max(f[i][j-1][k-1],f[i][j-1][k]))+a[i][j]+a[k][l];//l=i+j-k;

最终空间为50*50*50。

#include<iostream>
#include<cstdio>
using namespace std;
int a[60][60],f[60][60][60],n,m,i,j,k,l;
int main()
{
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++)
        for(j=1;j<=m;j++)
            scanf("%d",&a[i][j]);
    for(i=1;i<=n;i++)
        for(j=1;j<=m;j++)
            for(k=1;k<=i+j-1;k++){
                l=i+j-k;
                if(i==k&&j==l&& !(i==n&&j==m))
                    continue;
                f[i][j][k]=max(max(f[i-1][j][k-1],
                                   f[i-1][j][k]),
                               max(f[i][j-1][k-1],
                                   f[i][j-1][k]))+a[i][j]+a[k][l];
            }
    printf("%d",f[n][m][n]);
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值