[蓝桥杯 2014 省 AB] 地宫取宝
题目描述
X 国王有一个地宫宝库。是 n × m n \times m n×m 个格子的矩阵。每个格子放一件宝贝。每个宝贝贴着价值标签。
地宫的入口在左上角,出口在右下角。
小明被带到地宫的入口,国王要求他只能向右或向下行走。
走过某个格子时,如果那个格子中的宝贝价值比小明手中任意宝贝价值都大,小明就可以拿起它(当然,也可以不拿)。
当小明走到出口时,如果他手中的宝贝恰好是 k k k 件,则这些宝贝就可以送给小明。
请你帮小明算一算,在给定的局面下,他有多少种不同的行动方案能获得这 k k k 件宝贝。
输入格式
输入一行 3 3 3 个整数,用空格分开: n n n, m m m, k ( 1 ≤ n , m ≤ 50 , 1 ≤ k ≤ 12 ) k(1 \le n,m \le 50,1 \le k \le 12) k(1≤n,m≤50,1≤k≤12)。
接下来有 n n n 行数据,每行有 m m m 个整数 C i ( 0 ≤ C i ≤ 12 ) C_i(0 \le C_i \le 12) Ci(0≤Ci≤12) 代表这个格子上的宝物的价值。
输出格式
要求输出一个整数,表示正好取 k k k 个宝贝的行动方案数。该数字可能很大,输出它对 1000000007 ( 1 0 9 + 7 ) 1000000007(10^9+7) 1000000007(109+7) 取模的结果。
样例 #1
样例输入 #1
2 2 2
1 2
2 1
样例输出 #1
2
样例 #2
样例输入 #2
2 3 2
1 2 3
2 1 5
样例输出 #2
14
[题解] 👈(゚ヮ゚👈)
对于这道题目,小明在移动的过程中需要考虑4个变量:
当前坐标x
、y
,以及当前捡到的宝物的最大价值max
和已经捡到的总宝物数量count
因此我们可以定义一个状态表示:
// dp[x][y][max][count]移动到x y位置:捡到宝物的最大价值是max,捡到的宝物数量是count,从xy位置移动到终点恰好捡了k个宝物的总方案数
static int[][][][] dp=new int[55][55][55][55];
这个状态表示稍微有点复杂,可以慢慢理解。dp数组的初始化大小比题目的范围大一点即可。
0不能省略,因为价值为0的宝物,需要+1
根据状态表示,状态转移必须从地宫的终点位置往起点位置不断转移
。因此我们可以使用DFS,先深度搜索把靠终点位置的情况保存起来,然后回退逐步计算起点位置的值。
AC代码:
import java.util.Scanner;
// 类名必须是 Main,符合比赛或题目要求
public class Main1 {
//用于存储地宫
static int[][] g;
static int n,m,k;//地宫的行、列、恰好满足的宝物数量
static int mod=(int)1e9+7;//用来计算模数
// dp[x][y][max][count]移动到x y位置:捡到宝物的最大价值是max,捡到的宝物数量是count,从xy位置移动到终点恰好捡了k个宝物的总方案数
static int[][][][] dp=new int[55][55][55][55];
//用于初始化dp数组为-1,代表当前状态不存在
static void init(){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
for(int c=0;c<=20;c++){
for(int d=0;d<=20;d++){
dp[i][j][c][d]=-1;//表示不存在
}
}
}
}
}
public static void main(String[] args) {
//读入数据
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
m=sc.nextInt();
k=sc.nextInt();
g=new int[n+1][m+1];
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
g[i][j]=sc.nextInt()+1;//范围在1-13 0用来表示还没有拿任何宝物
}
}
init();
int ans=dfs(1,1,0,0);
System.out.println(ans);
}
static int dfs(int x,int y,int max,int count){
if(dp[x][y][max][count]!=-1) return dp[x][y][max][count];
//越界、拿的宝物大于k,直接返回0
if(x>n||y>m||count>k)return 0;
if(x==n&&y==m){
//不拿终点位置的宝物,恰好有k个,或者拿终点位置的宝物恰好有k个,返回1个方案
if(count==k||(count+1==k)&&max<g[x][y])return 1;
return 0;
}
int ans=0;//用来记录后面状态的总方案数
ans+=dfs(x+1,y,max,count);
ans%=mod;
ans+=dfs(x,y+1,max,count);
ans%=mod;
//如果可以拿当前位置的宝物,再累加
if(max<g[x][y]){
ans+=dfs(x+1,y,g[x][y],count+1);
ans%=mod;
ans+=dfs(x,y+1,g[x][y],count+1);
ans%=mod;
}
//状态转移(从终点到起点)
dp[x][y][max][count]=ans;
return ans;
}
}
宝物的价值,重新处理过一次,整体+1,原先范围是 C i ( 0 ≤ C i ≤ 12 ) C_i(0 \le C_i \le 12) Ci(0≤Ci≤12) ,现在的范围是 C i ( 1 ≤ C i ≤ 13 ) C_i(1 \le C_i \le 13) Ci(1≤Ci≤13)。原因在于,我们需要腾出一个状态用来表示起点位置(x=1,y=1)没有选择任何宝物,当前的选择的最大宝物的价值的无效值用max=0表示。避免状态转移出错。