方格取数(AcWing, NOIP2000)

文章讲述了如何使用二维动态规划解决一个经典问题,即两个人在网格中行走,每次只能向上下左右移动一格,且首次走过的位置数字变为0,目标是找到两人同时走到对角线终点时取到数字的最大值。
摘要由CSDN通过智能技术生成

题目描述

题目链接:

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)

https://www.acwing.com/problem/content/277/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值