题目要求:
给定一个𝑛∗𝑚的矩阵,每个单元都有一个值,现在从左上走到右下再回到左上,中间不走重复的点,求所走单元的和的最大值。
主要思路:
本题是求从矩阵左上角走到右下角再回到左上角,该路径的最大值,并且要求中间不走重复的点。先定义一个数组a[100][100]用来存储每个单元的值。如果按照题目要求去寻找路径,走的方向是不一样的,比较难于区分是否走过重复的点,所以将该题转化为从矩阵左上角到右下角,找出两条不同的路径,使得相加得到的值最大。
所以定义一个数组f[x1][y1][x2][y2],表示第一条路走到(x1,y1),第二条路走到(x2,y2)时候的最大值。一开始我写的状态方程是f[x1][y1][x2][y2]=max(f[x1-1][y1][x2][y2], f[x1][y1-1][x2][y2],f[x1][y1][x2-1][y2],dp[x1][y1][x2][y2-1])+a[x][y],但是这种情况下还会有重复的路径存在,看了一些资料之后我知道,这样的原因是因为状态转移方程在设计的时候,没有考虑到阶段性,两条路径没有同时出发,例如,对于f[2][2][3][3],它有可能经过了f[1][1][3][3],而f[1][1][3][3]又有可能经过了f[1][1][2][2],这样,(2,2)这个坐标既出现在了第一条路里,又出现在第二条路里。经过对状态转移方程稍作改动:f[x1][y1][x2][y2]=max(f[x1-1][y1][x2-1][y2],f[x1-1][y1][x2][y2-1],f[x1][y1-1][x2-1][y2],f[x1][y1-1][x2][y2-1])+a[x1][y1]+a[x2][y2]使得此时两条路的选择是同步的,以上四个f数组分别对应了到达(x1,y1,x2,y2)的四种情况。还可以发现一个规律:对于所有步数为s可以到达的坐标,x+y=s+2。所以,当两条路同步时,有x1+y1=x2+y2,变形一下,x1+y1-x2=y2
还有一个问题,如果两条路相交后,必然会存在重复的点。如在两个交叉点之间有两条路,我们可以交换两条路,而对于两个交叉点,让其中一条路绕一下,绕过这个重复的点,这样做,不仅可以使得两条图不存在重复的点,而且值还增大了,我们也可以反推出存在两个交叉点的路必定不是我们要找的路,还有比他更大的,所以完全没有必要考虑路径的交叉情况。
通过循环调用上述状态转移方程,不断将最大值记录到f[x1][y1][x2][y2]中,因为两条路要不同,所以一条由[n-1][m]即右下角的上方,一条由[n][m-1]即右下角的左边到达右下角,最后输出f[n][m-1][n-1][m]即为两条路的相加和的最大值。
#include <iostream>
using namespace std;
int a[100][100]; //存储每个单元的值
int f[100][100][100][100]; //f[x1][y1][x2][y2],表示第一条路走到(x1,y1),第二条路走到(x2,y2)时候的最大值。
int max(int a,int b,int c,int d)//四个数中取最大
{
if(b>a) a=b;
if(c>a) a=c;
if(d>a) a=d;
return a;
}
int main()
{
int n,m;
cout<<"请输入矩阵的行数和列数:";
cin>>n>>m;
cout<<"请输入矩阵中每个单元的值:";
for(int s=1;s<=n;s++)
{
for(int t=1;t<=m;t++)
{
cin>>a[s][t];
}
}
//转移方程,找到最优解
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int k=1;k<=n;k++)
for(int l=j+1;l<=m;l++)
f[i][j][k][l]=max(f[i-1][j][k-1][l],f[i-1][j][k][l-1],f[i][j-1][k][l-1],f[i][j-1][k-1][l])+a[i][j]+a[k][l];//转移方程
cout<<"两条路的所走单元的和的最大值为:"<<f[n][m-1][n-1][m]<<endl;//目标状态,因为两条路要不同,所以一条由[n-1][m]即右下角的上方,一条由[n][m-1]即右下角的左边到达右下角
return 0;
}