题目大意
- n*m的棋盘,每个格子有一个0-100的数值;
- 从左上角出发,只能向右和向下走,到达右下角;
- 从右下角出发,只能向左和向上走,到达左上角;
- 要求2次的路线不能重复,求经过格子的取值和尽可能大。
题目分析
- 体面非常直观,第一感觉用深搜就可以做,而且只有50的数据,感觉随便搞一搞还能暴力AC;
- 本题是在DP模块,所以还是用DP的思路来分析:
- 其实可以看作,从左上角去右下角2次,走了路线不同,这样就只有向右和向下两个方向,所以状态的推导就已经少了很多;
- 要走两次,这就是个纠结的问题,因为路线不能重复,可以理解为每次走两格:(x1,y1),(x2,y2);
思路1:四维DP
- f[x1][y1][x2][y2] 表示同时落脚到(x1,y1)和(x2,y2) 的最优解;
- 转移方程就需要考虑以下四种情况:
1)都从横的来;
2)都从竖的来;
3)1从横着来,2从竖着来;
4)1从竖着来,2从横着来; - 所以:
f[x1][y1][x2][y2]=max(f[x1][y1-1][x2][y2-1],f[x1-1][y1][x2-1][y2],f[x1][y1-1][x2-1][y2],f[x1-1][y1][x2][y2-1])+a[x1][y1]+a[x2][y2]; - 因为(x1,y1)和(x2,y2) 不可以重叠,所以枚举的时候做一个判断。
- 最后答案应该是:f[n][m-1][n-1][m]。
思路1参考代码
//luogu1006:传纸条:四维DP
#include<bits/stdc++.h>
using namespace std;
int f[60][60][60][60];
int a[60][60];
int n,m;
int maxx(int x,int y,int j,int k)//求4个数字的最大值
{
if(x<y) x=y;
if(x<j) x=j;
if(x<k) x=k;
return x;
}
int main()
{
memset(f,0,sizeof(f));
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
scanf("%d",&a[i][j]);
}
}
for(int x1=1;x1<=n;x1++)
{
for(int y1=1;y1<=m;y1++)
{
for(int x2=1;x2<=n;x2++)
{
for(int y2=1;y2<=m;y2++)
{
if(x1==x2&&y1==y2) continue;
f[x1][y1][x2][y2]=maxx(f[x1][y1-1][x2][y2-1],
f[x1-1][y1][x2-1][y2],
f[x1][y1-1][x2-1][y2],
f[x1-1][y1][x2][y2-1])+a[x1][y1]+a[x2][y2];
}
}
}
}
printf("%d",f[n][m-1][n-1][m]);
return 0;
}
思路2:优化成三维DP
- 基本思路和四维DP的没有变化,但是可以做一个简单的观察分析: 因为(x1,y1)和(x2,y2) 一定是从左上角出发,并且同步进行,所以一定是落在同一个右斜上
- 所以有:(x1+y1)等于(x2+y2)
- 因此,本来四层的循环,可以变为三层:i表示当前是第i斜,再分别枚举x1和x2,就可以直接算出对应的坐标了。
- 这样可以适用于数据增大到1000的测试点。
思路2参考代码:
//luogu1006:传纸条:三维DP
#include<bits/stdc++.h>
using namespace std;
int f[200][60][60];
int a[60][60];
int n,m;
int maxx(int x,int y,int j,int k)//求4个数字的最大值
{
if(x<y) x=y;
if(x<j) x=j;
if(x<k) x=k;
return x;
}
int main()
{
memset(f,0,sizeof(f));
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
scanf("%d",&a[i][j]);
}
}
for(int i=2;i<=n+m;i++)//枚举右斜
{
for(int x1=1;x1<=n;x1++)
{
if(i-x1<1) continue; //跳过y1坐标是负数的情况
for(int x2=1;x2<=n;x2++)
{
if(i-x2<1) continue; //跳过y2坐标是负数的情况
f[i][x1][x2]=maxx(f[i-1][x1][x2-1],
f[i-1][x1-1][x2],
f[i-1][x1][x2],
f[i-1][x1-1][x2-1])+a[x1][i-x1]+a[x2][i-x2];
if(x1==x2) //重复格子
{
f[i][x1][x2]-=a[x1][i-x1];
}
}
}
}
printf("%d",f[n+m][n][n]);
return 0;
}