试题 历届试题 地宫取宝 问题描述 X 国王有一个地宫宝库。是 n x m 个格子的矩阵。每个格子放一件宝贝。每个宝贝贴着价值标签。
地宫的入口在左上角,出口在右下角。
小明被带到地宫的入口,国王要求他只能向右或向下行走。
走过某个格子时,如果那个格子中的宝贝价值比小明手中任意宝贝价值都大,小明就可以拿起它(当然,也可以不拿)。
当小明走到出口时,如果他手中的宝贝恰好是k件,则这些宝贝就可以送给小明。
请你帮小明算一算,在给定的局面下,他有多少种不同的行动方案能获得这k件宝贝。 输入格式 输入一行3个整数,用空格分开:n m k (1<=n,m<=50, 1<=k<=12)
接下来有 n 行数据,每行有 m 个整数 Ci (0<=Ci<=12)代表这个格子上的宝物的价值 输出格式 要求输出一个整数,表示正好取k个宝贝的行动方案数。该数字可能很大,输出它对 1000000007 取模的结果。 样例输入
2 2 2 1 2 2 1样例输出
2样例输入
2 3 2 1 2 3 2 1 5样例输出
14[点击跳转至提交网址]
首先我做这题的时候看到有入口出口还有走法限制的“迷宫”时首先就想到dfs。问题是我从入口到出口的路径搜索很简单dfs(x,y)这样两个表示位置的参数我就能找到每一种状态。但是我不是找到路径就行,还得拿东西,并且拿东西的先后大小满足上升子序列,我们求能用nlogn的时间在一个长为n的数组找到它最长上升子序列的长度,但是他不是求最长,因为只要是一个长度大于等于K的上升序列就能通过组合数C(k,len)求出该路径上,第k个数取到该位置的方案数。 所以dfs+搜索每一条路径的最长上升子序列的长度+组合数,求出答案的方法不可行。
依据上面,我就想是不是可以边搜索边枚举每一个位置上的物品拿与不拿?如果直接暴力搜当然是不行的。 那么怎么减少重复的搜索状态呢?我们就必须把每一个状态保存下来(就是动态规划的思想),如果当前状态已经搜索过就直接返回上次记录的结果(记忆化搜索)。
保存搜索结果的数组,因为每个状态有位置(x,y),当前已取个数s,最大价值maxx,4个信息才能记录一个状态,故使用一个4为数组:dp[x][y][s][maxx] 表示在(x,y)位置,取s个物品,且最大价值为maxx的方案数。
状态转移: dp[i][j][s][maxx]= 1.取当前物品,且满足当a[i][j]>max才能取 dp[i+1][j][s+1][a[i][j]] + dp[i][j+1][s+1][a[i][j]] 2.不取当前物品 dp[i+1][j][s][maxx] + dp[i][j+1][s][maxx]
代码里有详细注释,应该不难理解。
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<algorithm> using namespace std; #define ll long long int ll const mod=1000000007; int a[55][55]; int next[2][2]={{0,1},{1,0}}; int book[55][55];//dfs int n,m,k; ll dp[55][55][13][13];//记录搜索结果 ll dfs(int x,int y,int s,int maxx) { if(dp[x][y][s][maxx+1]!=-1)//因为maxx可能为-1 { return dp[x][y][s][maxx+1]; }//记忆化搜索 if(x==n&&y==m) { if((s==k)||(s==k-1&&maxx<a[x][y]))//到[n][y]时,k个去满了和差一个时把[x][y]取了 return dp[x][y][s][maxx+1]=1ll; return dp[x][y][s][maxx+1]=0ll; } ll res=0ll; //dfs for(int i=0;i<2;i++){ int nx=x+next[i][0]; int ny=y+next[i][1]; if(nx<1||nx>n||ny<1||ny>m) continue; //if(book[nx][ny]) continue;//dp就相当于标记,故无需标记 //1.不取a[nx][ny] res+=dfs(nx,ny,s,maxx); //2.取a[nx][ny] if(a[x][y]>maxx&&s<k) res+=dfs(nx,ny,s+1,a[x][y]);//注意不是a[nx][ny] ,因为当前搜索的位置(x,y)还没考虑 } //记录搜索结果 dp[x][y][s][maxx+1]=res%mod; return dp[x][y][s][maxx+1]; }//后面向前返回搜索结果 int main(void) { memset(dp,-1,sizeof(dp));//dp初始化为-1 scanf("%d %d %d",&n,&m,&k); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&a[i][j]); //0<=maxx<=12 dfs(1,1,0,-1);//dp[i][j][k][max]; printf("%lld\n",dp[1][1][0][0]); return 0; }其实记忆化搜索和动态规划就是一个东西来的,都是保存搜索状态。前者是减少重复搜索,后者是用空间换取时间。 详细题解请移步: https://www.acwing.com/solution/acwing/content/7116/
