方格取数(洛谷P7074)

https://www.luogu.com.cn/problem/P1004
这道题说是要求两条从A到B的路径,路径上的数会被取走,(一个数字只能被取一次)使取走的数字和最大,并输出这个最大值。
看完就能发现核心内容如下
(1)A到B的两条路径
这个很好解决,只要两个二重循环分别枚举两条路径就行
(2)一个数被取走后变为0
换言之,一个数只能被两条路径中的一条经过,因此,我们就发现两层循环分别枚举两条路径是不可行的,
因为这个新的条件显然使两条路径上的各点坐标产生了一定的特殊关系 or 在方程转移的过程中取数的操作有一些特殊要求,以保证此条件成立。

既然我们发现这两条路径不是相互独立的,那么就能够想到把用一个四重循环来同时枚举两条路径

初步dp式:dp[i][j][k][q] = max{dp[i][j][k][q],max(dp[i-1][j][k-1][q],dp[i-1][j][k][q-1],dp[i][j-1][k-1][q],dp[i][j-1][k][q-1])+sav[i][j] + sav[k][q]}
两个点的转移一共有四种情况(左左,左上,上左,上上)

这个dp式显然还是没有对两条路径进行限制,因此我们下一步要做的就是把这个限制补充上去

(1)首先会想到有可能是对两个点的坐标进行特判
即if(i != k && j != q)才进行转移,否则不转移
但是简单想一下就会发现这么做肯定不行,因为很有可能会出现最优情况中两条路径交叉
这个思路就被放弃了
但是其他更复杂的点的关系并不能被找到
(2)因此我们就选择对转移过程中取数这一操作进行调整
想像一下,如果两条路径在枚举过程中,同时枚举到了同一个点,因此这个点上的数值就被取了两次,这肯定是不对的。
所以我们此时加一个特判:如果两点重合,那么这个点的数值只取一次

正确代码就有了

#include<cstdio>
#include<algorithm>
using namespace std;
int main()
{
	int n, sav[15][15] = {}, dp[15][15][15][15] = {}, a, b, c;
	scanf("%d", &n);
	do
	{
		scanf("%d%d%d", &a, &b, &c);
		sav[a][b] = c;
	}while(a != 0);
	for(int i = 1;i <= n;i++)
	{
		for(int j = 1;j <= n;j++)
		{
			for(int k = 1;k <= n;k++)
			{
				for(int q = 1;q <= n;q++)
				{
					if(i != k && j != q)
					{
						dp[i][j][k][q] = max(dp[i][j][k][q],max(dp[i-1][j][k-1][q],max(dp[i-1][j][k][q-1],max(dp[i][j-1][k-1][q],dp[i][j-1][k][q-1]))) + sav[i][j] + sav[k][q]);	
					}
					else
					{
						dp[i][j][k][q] = max(dp[i][j][k][q],max(dp[i-1][j][k-1][q],max(dp[i-1][j][k][q-1],max(dp[i][j-1][k-1][q],dp[i][j-1][k][q-1]))) + sav[i][j]);
					}
				}
			}
		}
	}
	printf("%d", dp[n][n][n][n]);
	return 0;
}	
/*
8
2 3 13
2 6 6 
3 5 7
4 4 14
5 2 21
5 6 4
6 3 15
7 2 14
0 0 0*/ 

一点其他的废话:
(1) 有人可能回想在操作过程中对取过的数打标记,来保证这个数不会被取多次。
如果要写递推格式的话这种操作肯定是不行的,记忆化搜索到是可以这么干,因为记忆化搜索可以回溯但是递推显然不能啊。
总结一下:需要回溯的操作就尽量不要在递推里尝试了,因为极有可能(基本可以说是一定)不能实现
(2)我的做题历程:
先写了if(i != k && j != q)才转移的谈判
然后就发现输出全是0(根本就不会进入最后的共同重点啊)
于是删了特判,发现输出的答案变大了,说明有一个点被取了多次(感谢这个善良的样例体现出了这个问题)
然后就写出了正解
感觉这道题还是挺典型的

posted @ 2021-06-19 18:10  Mint-hexagram  阅读(154)  评论(1编辑  收藏  举报