题意
有 n×n 的矩形,价值为 ai,j。
现在你要从 a1,1 ,通过移动到 ai,j+1,ai+1,j 的方式,走到 an,n。
再通过移动到 ai,j−1,ai−1,j 的方式,回去 a1,1。定义价值为路径上 ai,j 的和,重复走过的路径只计算一次。
问最大的价值是多少?
思路
错解
首先,猜个结论:先从 1,1 跑 dp。然后按路径将 ai 设为 0。在从 an,n 跑 dp。
但是这样做是错误的。
3
5 5 1
0 5 -2
5 -4 -2
dp 会跑出来 11+1 的结果。但是正解却是 4+10。
dp:
(1,1)→(1,2)→(2,2)→(2,3)→(3,3)→(3,2)→(3,1)→(2,1)→(1,1)
5+5+5−2−2=11,0−4+5+0+0=1
正解:
(1,1)→(2,1)→(3,1)→(3,2)→(3,3)→(3,2)→(2,2)→(2,1)→(1.1)
5+0+5−4−2=4,0+0+5+5+0=10
动态规划
此时我们就举出了反例。还是老实用朴素的 dp 吧。本题可以理解为有两个人从 (1,1) 走到 (n,n)。那就可以将状态设为 dpx,y,xx,yy。分别是第一个人的坐标,第二个人的坐标。
我们使两个人一起走,如果两人坐标相同,就只算一遍。这个时候的状态转移方程即让两个人各走一步:
dpx,y,xx,yy=max{dpx−1,y,xx−1,yy,dpx−1,y,xx,yy−1,dpx,y−1,xx−1,yy,dpx,y−1,xx,yy−1}
但是时空复杂度:Θ(n4)。
优化
考虑优化,dp 优化的本质就是削减无用状态与重复转移。我们注意到,只有 x+y=xx+yy 的状态是有意义的,因为如果不同,说明不符合两个人一起走的定义。
假如两个人同时走,那么他们的坐标和就会相同。设坐标和为 s。那么枚举 s,x,xx。y,yy 就可以算出来了。此时可设 dps,x,xx 为坐标和为 s,第一个人在 (x,s−x),第二个人在 (xx,s−xx)。
接下来思考状态转移方程,其实就是由朴素版推出来的:
dps,x,xx=max{dps−1,x−1,xx,dps−1,x,xx−1,dps−1,x−1,xx−1,dps−1,x,xx}
复杂度就变成 Θ(n3) 。
初始值
在开始的时候,我们要将所有状态设为不可达。但是 dp2,1,1 的状态是确定的。dp2,1,1←a1,1。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 3e2+1;
int a[N][N],dp[N<<1][N][N],n;
int main() {
memset(dp,-0x3f,sizeof dp);
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
cin>>a[i][j];
}
}
dp[2][1][1]=a[1][1];
for(int i=3;i<=n<<1;i++) {
for(int j=1;j<=n;j++) {if(i-j<1||i-j>n) continue;
for(int k=1;k<=n;k++) {if(i-k<1||i-k>n) continue;
int t=0;
if(j==k) t=a[j][i-j];
else t=a[j][i-j]+a[k][i-k];
dp[i][j][k]=max(max(dp[i-1][j-1][k],dp[i-1][j][k-1]),max(dp[i-1][j][k],dp[i-1][j-1][k-1]))+t;
}
}
}
cout<<dp[n<<1][n][n];
return 0;
}