【洛谷】P8612 [蓝桥杯 2014 省 AB] 地宫取宝 的题解
洛谷传送门
思路
-
第一眼审题看到从左上角走到右下角,dfs。
-
再继续看题目,有限定条件:
-
走到一个位置依据已拿宝物的最大价值 m a x n maxn maxn 选择是否拿宝。
-
只有走到右下角且拿宝数 c n t = k cnt = k cnt=k 的路径才有效。
- 所以第一步先把左上到右下的深搜写出来。
- 因为待会涉及到一个宝物最大价值 m a x n maxn maxn 和拿宝数 c n t cnt cnt 的问题,所以 dfs 传入四个参数。
- 但是光这样的条件深搜是得到满分的,所以还得优化。
-
通过对路径位置之间的关系分析我们可以知道:
-
在四个参数不变的前提之下,这个状态的 dfs 还是会被计算多次。
-
从不同方向到达 ( x , y ) (x,y) (x,y) 点,如果之前的拿宝数都是
cnt - 1
,而且 m a x n maxn maxn 都要小于 ( x , y ) (x,y) (x,y) 位置的宝物价值a[x][y]
,那就会重复计算(x, y, a[x][y], cnt)
。更别说有可能在几步之前取宝数达到cnt - 1
,然后后面几步不拿宝,到 ( x , y ) (x,y) (x,y) 再拿宝到达 c n t cnt cnt 这个数值。
- 因此这道题的重复计算是非常恐怖的,我们可以考虑用一个四维数组将已经计算出来的值存下来。
-
这样就可以在到达一个搜索状态的时候,先去判断这个状态是否已经计算,如果计算过就直接返回之前的结果。
-
至于为什么是四维数组,这是因为 dfs 的参数有四个,四维分别对应 dfs 的四个参数。
注意点:因为宝物价值是有可能为 0 0 0 的,所以 m a x n maxn maxn 的初值设定成了 − 1 -1 −1,但数组是没有负下标的,因此在记忆的时候选择将 m a x n maxn maxn 的下标 + 1 +1 +1,通过偏移量来解决数组越界问题。
代码
#include <bits/stdc++.h>
#define endl "\n"
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
int n, m, k;
ll vis[51][51][15][13], a[55][55];
ll dfs(int x, int y, int maxn, int cnt) {//记忆化搜索
ll res = 0;
//到达边界或者取宝数大于k
if(x > n || y > m || cnt > k) return 0;
//已经计算过,直接返回
if(vis[x][y][maxn + 1][cnt] != -1) return vis[x][y][maxn + 1][cnt];
//边界出口
if(x == n && y == m) {
if(cnt == k || (cnt == k - 1 && a[n][m] > maxn)) return 1;
return 0;
}
//拿或不拿分别两种走法
if(a[x][y] > maxn) {
res += dfs(x + 1, y, a[x][y], cnt + 1);
res += dfs(x, y + 1, a[x][y], cnt + 1);
}
res += dfs(x + 1, y, maxn, cnt);
res += dfs(x, y + 1, maxn, cnt);
//结果记忆并返回
return vis[x][y][maxn + 1][cnt] = res % mod;
}
int main() {
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
memset(vis, -1, sizeof(vis));
cin >> n >> m >> k;
for(int i = 1; i <= n; i ++) {
for(int j = 1; j <= m; j ++) {
cin >> a[i][j];
}
}
cout << dfs(1, 1, -1, 0);
return 0;
}