题目描述
在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙。反之亦然。
还有一件事情需要注意,全班每个同学愿意帮忙的好感度有高有低(注意:小渊和小轩的好心程度没有定义,输入时用0表示),可以用一个0-1000的自然数来表示,数越大表示越好心。小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这两条路径上同学的好心程度之和最大。现在,请你帮助小渊和小轩找到这样的两条路径。
输入
接下来的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;
#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]); }