题目描述
题目链接:
https://www.acwing.com/problem/content/description/1029/
思路:
经典的动态规划问题,第一次做其实还是比较有难度的。
难点主要在于: 第一次走过的格子中的数字会被置0。
使用普通的2维动态规划是没有办法刻画上面这个事情的,为此只能使用更高的维度,同时维护两个人的行为,用dp数组维护两个人同时走时取到的数字的最大值。
f[k][x][y]的含义: 表示两个人同时走,横纵坐标之和为k, 第一个人当前位置为:(x,k-x), 第二个人当前位置为: (y,k-y) 时。两个人走到同一个位置时,权值只加1次;两个人走到不同的位置时,分别将两个人的权值都加上。 f[k][x][y]表示满足上述条件的两个人走的路线上取得数字的最大值。
目标: f[2n][n][n]. 初始值: f[2][1][1]=a[1][1].
dp含义正确性的证明:我们通过上面的dp,能够得到正确答案。 考虑构造一个双射(一一对应)
一方面,考虑任何一条满足条件的走法(从A点走两次到B点), 设这两条路径为L1,L2. 我们可以令两个人同时分别走路线L1和路线L2,他们会同时到达B点。
显然,后者得到的答案和前者是相同的。 故可以由任何一个满足条件的走法映射到上面的dp数组
另一方面,对于上面的dp数组,其含义为两个人同时走,走到B点。 他们的走法显然是一种合法的走法,且权值是按照题目的要求进行计算的(同一个位置只算一次),故可以用上面的dp数组映射到一种合法方案。
综合两方面来看,我们就得到了“dp数组”和“题目要求的合法走法”的一一映射。
dp更新: 采用向后更新比较方便,考虑当前状态(k,x,y), 他会有4个后继状态: (k+1,x,y), (k+1,x+1,y), (k+1,x,y+1), (k+1,x+1,y+1). 用当前状态去更新他所有的后继状态,当且仅当后继状态两个人的 位置重合时,仅加一次数字值; 其余情况,均是分别加上第一个人位置上的值和第二个人位置上的值。
用循环来实现上面说的更新策略,最外层循环为k, 其次为x, 其次为y.
这样子更新一定是正确的,因为所有能够更新f[k][x][y]的状态一定是k-1层的,而k-1层的状态一定先于当前状态被遍历,故一定能够保证所有k-1层的状态都已经把我更新了,因此当遍历到f[k][x][y]时,他此时存的一定是正确答案。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=11;
const int dire[2][2]={{1,0},{0,1}};
int f[2*N][N][N];
int n;
int a[N][N];
int main(void){
scanf("%d",&n);
int x,y,z;
while(scanf("%d%d%d",&x,&y,&z)!=-1){
if(x==0 && y==0 && z==0) break;
a[x][y]=z;
}
f[2][1][1]=a[1][1];
for(int k=2;k<=2*n;k++){
for(int i=1;i<k&&i<=n;i++){
for(int j=1;j<k&&j<=n;j++){
// f[k][i][j] update other
int x_1=i;
int y_1=k-i;
int x_2=j;
int y_2=k-j;
if(y_1<1||y_1>n||y_2<1||y_2>n) continue;
for(int dd=0;dd<2;dd++){
int dx_1=x_1+dire[dd][0];
int dy_1=y_1+dire[dd][1];
if(dx_1<1||dx_1>n||dy_1<1||dy_1>n) continue;
for(int gg=0;gg<2;gg++){
int dx_2=x_2+dire[gg][0];
int dy_2=y_2+dire[gg][1];
if(dx_2<1||dx_2>n||dy_2<1||dy_2>n) continue;
if(dx_2==dx_1 && dy_1==dy_2) f[k+1][dx_1][dx_2]=max(f[k+1][dx_1][dx_2],f[k][x_1][x_2]+a[dx_1][dy_1]);
else f[k+1][dx_1][dx_2]=max(f[k+1][dx_1][dx_2],f[k][x_1][x_2]+a[dx_1][dy_1]+a[dx_2][dy_2]);
}
}
}
}
}
int ans=f[2*n][n][n];
printf("%d",ans);
return 0;
}
拓展:
通过以上方法,还可以解决“传纸条”问题(NOIP 2008)