[蓝桥杯][历届试题]地宫取宝
Description
X 国王有一个地宫宝库。是 n x m 个格子的矩阵。每个格子放一件宝贝。每个宝贝贴着价值标签。
地宫的入口在左上角,出口在右下角。
小明被带到地宫的入口,国王要求他只能向右或向下行走。
走过某个格子时,如果那个格子中的宝贝价值比小明手中任意宝贝价值都大,小明就可以拿起它(当然,也可以不拿)。
当小明走到出口时,如果他手中的宝贝恰好是k件,则这些宝贝就可以送给小明。
请你帮小明算一算,在给定的局面下,他有多少种不同的行动方案能获得这k件宝贝。
Input
输入一行3个整数,用空格分开:n m k (1 ≤ n,m ≤ 50, 1 ≤ k ≤ 12)。
接下来有 n 行数据,每行有 m 个整数 Ci (0 ≤ Ci ≤ 12) 代表这个格子上的宝物的价值。
Output
要求输出一个整数,表示正好取k个宝贝的行动方案数。该数字可能很大,输出它对 1000000007 取模的结果。
Sample Input
2 2 2
1 2
2 1
Sample Output
2
1 #include<iostream>
2 #include<cstring>
3 #define N 1000000007
4 using namespace std;
5 int n,m,k;
6 int map[103][103];
7 int dp[53][53][15][15];/* 6 */
8 int dir[4][2]={1,0,0,1};
9 int dfs(int x,int y, int num,int maxa){
10 if(dp[x][y][num][maxa+1]!=-1)/* 1*/ //如果这种情况之前已经标记过了,就不用再继续搜索了,直接使用这个值
11 return dp[x][y][num][maxa+1];
12 if(x==n&&y==m){//如果到达出口了,
13 if(num==k||(num==k-1&&maxa<map[x][y]))/* 2 */ //如果满足这两种情况
14 return dp[x][y][num][maxa+1]=1;//就把这数组赋值为1
15 else
16 return dp[x][y][num][maxa+1]=0;//否则为零
17 }
18 long long s=0;/* 5 */ //每进入一次dfs就要初始化s为0
19 int dx,dy;
20 for(int i=0;i<2;i++){
21 dx=x+dir[i][0];
22 dy=y+dir[i][1];//确定下一个位置的坐标
23 if(dx<=n&&dy<=m){/* 3 */ //如果这个位置在图的范围内
24 if(maxa<map[x][y])/* 4 */ //如果这个位置的宝物可以拿
25 s+=dfs(dx,dy,num+1,map[x][y]);//就把宝物拿上然后搜索
26 s+=dfs(dx,dy,num,maxa);//这是不拿宝物的搜索
27 }
28 }
29 return dp[x][y][num][maxa+1]=s%N;
30 }
31 int main(){
32 scanf("%d%d%d",&n,&m,&k);
33 for(int i=1;i<=n;i++){
34 for(int j=1;j<=m;j++){
35 scanf("%d",&map[i][j]);
36 }
37 }
38 memset(dp,-1,sizeof(dp));
39 printf("%d",dfs(1,1,0,-1));
40 return 0;
41 }
这道题的方法就是记忆化搜索
直接用搜索会超时
以下是对代码中/* */地方的注释
/* 6 */ 解释下dp数组的意思,举例说明 dp[3][4][5][6]=7 代表的意思是
当小明走到第3行第3列(从第0行第0列开始算)的格子的时候,小明身上有5件宝贝,在这5件宝贝中,
价值最高的那件宝贝的价值是6,在这样的情况下,小明走到终点拿到k件宝物的方案数是7
这也体现了记忆化搜索的特点,因为我们要记录在搜索过程中的信息,这个dp数组记录的信息就是
位置,此时的宝贝数量,还有价值最高的宝贝的价值,这些信息就足够分明每一种情况,在这样的情况下,
从这个格子开始搜索,得到的结果都是一样的,那么每种情况就只搜索一次就行了,下次再遇到这种情况,
就直接用结果就行了
那么为什么要记录最大值这个信息呢,因为题目中有个条件是,当这个格子里的宝物的价值大于小明
手上拿的任意的宝物的价值的话,小明就可以拿走这个格子里的宝物,那么当这个格子里的宝物大于小明
手上拿的最大价值的宝物的价值的话,这个格子里的宝物的价值就一定大于小明手上拿的任意的宝物的价值
记录最大价值这条信息就是为了方便判断是否符合这个条件,否则你还得拿每一个小明手中的宝物来和格子里
的宝物相比较,那样太麻烦了。
在这里还要说一下,代码里的dp数组里的第四维是maxa+1,为什么这样呢,因为最开始的时候我们设置的maxa的值为
-1,但-1是没办法做下标的,所以就加上了1,这样就可以做下标了,所以其实dp[3][4][5][6]里的意思是
在第三行第四列的格子的时候,小明有5个宝贝,5个宝贝中的价值最大的宝贝的价值是6-1=5
那么为什么maxa一开始要设置为-1呢,其实在最一开始的时候,小明手中没有宝物,那自然也没有
最大价值这一说,所以任意一个格子里的宝物的价值都大于此时一无所有的小明的手中宝物的价值
所以只要把maxa设成一个比任何宝物的价值都小的数就行了,因为宝物的价值有可能是0
所以maxa设成0是不行的,又因为maxa还要做下标用,设成太小的数的话也不太好,比如说你
把maxa设置成-10086的话,此时maxa在当下标用的话,就得设成dp[x][y][num][maxa+10086]
这样才能保证下标不出现负数,但这样不就太麻烦了嘛,😝
所以设置成-1就行了。
/* 1 */ 这就是记忆化搜索中记忆的部分,一开始dp数组的值都是-1,如果dp数组的值不是-1了,
那就代表这个地方被搜索过了,那么我们就不用再搜索一遍了,我们直接用存储再这个数组里的值就行了,
这就相当于从这开始搜索了一编
/* 2 */ 解释下这两种情况都是什么,
第一种情况:小明走到了出口,并且手上的宝物整好有k件,
第二种情况:小明走到了出口,手上的宝物有k-1件,并且此时小明可以拿走出口格子里的宝物,
那么当小明拿走出口格子的宝物之后,小明手上就有了k件宝物
/* 3 */ 一般的搜索中,主人公都是可以上下左右的走,所以设置限制条件的话,都是这样的
dx>=1&&dx<=n&&dy>=1&&dy<=m
但在这道题中,主人公只能向右和向下走,所以只要判断它会不会超过右边的边界和下面的边界就行了
所以条件就是
dx<=n&&dy<=m
就行了
/* 4 */ 这个前面说到了,当这个格子的宝物的价值大于小明手上拿的价值最大的宝物的价值的时候,
小明就可以拿走这个格子里的宝物了
/* 5 */ s存储的是当前这个格子到终点有多少条行动方案能得到k件宝物,一个格子的行动方案数等于
它右边格子的行动方案数加上它下边格子的行动方案数,并且在向右边或下边探索的时候,分别有两种情况
一种是拿了这个格子里的物品再向右边或下边探索,另一种是没拿这个格子里的物品再继续向右边或下边
探索
另外,在定义s的时候得用long long 用int会越界
O(∩_∩)O