传纸条(动态规划)

时间限制: 1 Sec  内存限制: 128 MB

题目描述

    小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题。一次素质拓展活动中,班上同学安排做成一个m行n列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了。幸运的是,他们可以通过传纸条来进行交流。纸条要经由许多同学传到对方手里,小渊坐在矩阵的左上角,坐标(1,1),小轩坐在矩阵的右下角,坐标(m,n)。从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递。

    在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙。反之亦然。


    还有一件事情需要注意,全班每个同学愿意帮忙的好感度有高有低(注意:小渊和小轩的好心程度没有定义,输入时用0表示),可以用一个0-1000的自然数来表示,数越大表示越好心。小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这两条路径上同学的好心程度之和最大。现在,请你帮助小渊和小轩找到这样的两条路径。

输入

输入第一行有2个用空格隔开的整数m和n,表示班里有m行n列(1<=m,n<=50)。
接下来的m行是一个m*n的矩阵,矩阵中第i行j列的整数表示坐在第i行j列的学生的好心程度。每行的n个整数之间用空格隔开。

输出

输出共一行,包含一个整数,表示来回两条路上参与传递纸条的学生的好心程度之和的最大值。

样例输入

3 3
0 3 9
2 8 5
5 7 0

样例输出

34

提示

来源


这一道题的思路很简单,暴力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 <stdio.h>
#include <algorithm>
#include <cmath>
#include <math.h>
#include <cstring>
#include <string>
#include <queue>
#include <deque>
#include <stack>
#include <stdlib.h>
#include <list>
#include <map>
#include <utility>
#include <set>
#include <bitset>
#include <vector>
#define pi acos(-1.0)
#define inf 0x3f3f3f3f
#define linf 0x3f3f3f3f3f3f3f3fLL
#define ms(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
int gcd(int x, int y)
{
    if(y==0)return x;
    return gcd(y, x%y);
}
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 <stdio.h>
#include <algorithm>
#include <cmath>
#include <math.h>
#include <cstring>
#include <string>
#include <queue>
#include <deque>
#include <stack>
#include <stdlib.h>
#include <list>
#include <map>
#include <utility>
#include <set>
#include <bitset>
#include <vector>
#define pi acos(-1.0)
#define inf 0x3f3f3f3f
#define linf 0x3f3f3f3f3f3f3f3fLL
#define ms(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
int gcd(int x, int y)
{
    if(y==0)return x;
    return gcd(y, x%y);
}
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<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int a[52][52],f[52][52][52],n,m,i,j,k,l,sum;
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;sum=a[i][j]+a[k][l];
                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]))+sum;
            }
    printf("%d",f[n][m][n]);
}



猜你喜欢

转载自blog.csdn.net/qq_41021816/article/details/80424798