题面:
背景
JerryZhou同学经常改编习题给自己做。
这天,他又改编了一题。。。。。
描述
设有N*N的方格图,我们将其中的某些方格填入正整数,
而其他的方格中放入0。
某人从图得左上角出发,可以向下走,也可以向右走,直到到达右下角。
在走过的路上,他取走了方格中的数。(取走后方格中数字变为0)
此人从左上角到右下角共走3次,试找出3条路径,使得取得的数总和最大。
格式
输入格式
第一行:N (4<=N<=20)
接下来一个N*N的矩阵,矩阵中每个元素不超过80,不小于0
输出格式
一行,表示最大的总和。
听...听说是一类经典DP...
反正我是不会...
一开始我的思路就很一根筋:直接按走一次做,然后重复三遍
然后,然后就听取WA声一片...
反正最后也没想出来,是看的题解的思路...呜呜呜我太菜了
有一个比较重要的结论是:
在矩阵中,如果只能向下走或者向右走,那么,知道起点的前提下,步数,行数,列数,知道其中两个能推第三个。
并且,走到同一位置的两个点,必定花费了相同的步数。
这些结论支撑起了我们的思路:让三个点同时走。
下面试着构建状态:f[x][i][j][k],代表走了x步,i,j,k代表三个人分别走到了第i行,第j行,第k行。
其实也就等价于走到了第i行第x-i+2列,第j行第x-j+2列,第k行第x-k+2列。
对于每一个状态,这个状态之前最多会有8种状态(每个点分别从左边和从上边过来),也就是说,要从这8个状态中选一个最好的状态来更新当前状态。
写成方程就是:
f[x,i,j,k]=max(f[x-1,i-1,j,k],f[x-1,i,j-1,k],f[x-1,i,j,k-1], //其中一个从上边来 f[x-1,i-1,j-1,k],f[x-1,i-1,j,k-1],f[x-1,i,j-1,k-1], //其中两个从上边来 f[x-1,i-1,j-1,k-1], //三个都从上边来 f[x-1,i,j,k]) //三个都不从上边来 +add //对应需要加上的 一个/两个/三个 点的值
(代码里没特判边界是因为无效的前一个状态对应的f是0,也就是说,如果有任何一个合法的上一个状态都要比它大)
由于格子里的数被取走就没了,所以要判断有没有任意两/三个点走到了同一个格子,不要加多了。(我就在这里犯傻了...三个位置两两不同是:i!=j&&j!=k&&i!=k,我当成了i!=j&&j!=k就行...)
代码如下:
#include <cstdio> #include <cstdlib> int N; int v[21][21]; int f[41][21][21][21]; int max(int a,int b){return a>b?a:b;} void dp(){ for(int x=1;x<=2*N-2;x++){ for(int i=1;i<=x+1&&i<=N;i++){ for(int j=1;j<=x+1&&j<=N;j++){ for(int k=1;k<=x+1&&k<=N;k++){ int add; if(i==j&&j==k)add=v[i][x-i+2]; if(i==j&&j!=k)add=v[i][x-i+2]+v[k][x-k+2]; if(i!=j&&j==k)add=v[i][x-i+2]+v[j][x-j+2]; if(i==k&&j!=k)add=v[i][x-i+2]+v[j][x-j+2]; if(i!=j&&j!=k&&i!=k)add=v[i][x-i+2]+v[j][x-j+2]+v[k][x-k+2]; f[x][i][j][k]=max(f[x-1][i-1][j][k],max(f[x-1][i][j-1][k],max(f[x-1][i][j][k-1], max(f[x-1][i-1][j-1][k],max(f[x-1][i-1][j][k-1],max(f[x-1][i][j-1][k-1] ,max(f[x-1][i-1][j-1][k-1],f[x-1][i][j][k])))))))+add; } } } } } int main(){ scanf("%d",&N); for(int i=1;i<=N;i++){ for(int j=1;j<=N;j++) scanf("%d",&v[i][j]); } f[0][1][1][1]=v[1][1]; dp(); printf("%d",f[2*N-2][N][N][N]); return 0; }
原文地址:https://www.cnblogs.com/Zarax/p/12041655.html