1. 问题描述:
有一个地宫宝库。是 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
2. 思路分析:
① 对于这种走迷宫的这一类问题,典型的做法是使用深度优先搜索,因为它可以尝试每一种的可能性,搜索出所有可能的路径
分析题目可以知道对于当前物品假如我手中的最大价值小于了当前格子的最大价值,那么我就可以选择拿取当前的物品,但是也可以选择不拿取,所以就存在着两种可能的选择,格子物品价值大于了我手中的价值选择拿取,当格子物品大于,等于或者小于的这些情况我选择不拿取当前的物品,所以在方法中需要使用一个变量来记录当前我手中的最大价值,还有就是我当前手中拿取的物品的数量是多少,这样等一下在出口的时候才好判断,对于方法中需要传入的参数我们是先分析问题然后结合问题和相应的出口的判断来确定这些参数的
② 所以根据上面的分析我们可以知道可以使用一个if判断我当前是否可以拿取当前格子的物品,还有其他的平行状态是不需要进行判断的,其余的情况我是不拿取当前的物品的,所以不需要进行if判断,这种情况是我是可以走的
③ 出口的设计,当发现我们走到最后一个格子的时候我们面临着两个选择,一个是我手中拿的物品的数量已经满足了k件了那么我直接走出迷宫,当我手中的物品的数量少于k件的时候那么我要判断最后一个格子的价值是否大于了手中的最大价值假如满足条件那么我可以拿走格子里的物品走出迷宫,这两种条件是或者的关系满足任何一个条件我都可以走出迷宫
④ 但是使用上面的普通的递归对于数据规模大的时候完全计算不出来,所以需要使用记忆性的递归来帮助我们降低时间复杂度,记忆性的递归往往是为了避免子问题的重复求解的情况,对于这道题目来说我们知道假如我们走到一个格子的时候此时x坐标,y坐标,手中的最大价值和手中拿取的物品的数量是一样的那么继续往下递归就是重复求解了,因为我们不同的路径是可以可能到达同一点,而不同路径到达同一点的时候这个时候的两条路径进行一样的递归这样就造成了子问题的重复求解了,所以为了避免这个问题我们需要声明一个四维的数组来进行记录,第一个维度是x横坐标,第二个维度是y纵坐标,第三个维度是当前我手中拿取的最大价值,第四个维度是当前我手中拿取的物品的数量,这样当我们发现四个维度对应的有值的时候那么直接返回就好了
⑤ 但是怎么样进行使用呢?我们递归方法一开始的时候需要先进行查询看一下当前求的对应的四维数组中有没有记录这个值,假如有的话那么直接返回就好了,而不需要进行重复的求解,需要注意的是这里有一个坑就是物品的价值是可以为零的,所以我们我们一开始的时候传进方法里面的最大价值是-1这样假如当前格子的物品的价值是零的时候我们也可以拿取,但是这里就有一个问题就是数组下标不能够为负数,这里我们采用的是使用max + 1对应的位置来进行处理,这样数组就比物品的最大价值要多一个长度
⑥ 需要注意的一个点是我们在递归方法的时候需要将计数变量置为零,因为对于当前的格子x,y来说我当前的能够走的方法数为零,在往下走的时候满足条件才会进行累加,所以不能够使用一个全局变量来进行累加否则每一次递归都造成了对全局变量的修改这样做是不对的
⑦ 对于声明一个四维数组对于数据规模小的时候还行,因为51 * 51 * 14 * 14 * 4(字节) / 1024 / 2024MB的内存消耗,所以说还是可以的
3. 具体的代码如下:
import java.util.Scanner;
public class Main {
static int arr[][];
static int [][][][] rec = new int[51][51][14][14];
static int m;
static int n;
static int k;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
k = sc.nextInt();
arr = new int[n][m];
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
arr[i][j] = sc.nextInt();
}
}
//首先要对四维数组进行初始化,所有值置为一
for(int i = 0; i < 51; i++){
for(int j = 0; j < 51; j++){
for(int l = 0; l < 14; l++){
//注意变量不能为k否则与全局变量k产生了冲突
for(int o = 0; o < 14; o++){
rec[i][j][l][o] = -1;
}
}
}
}
long count = dfs(0, 0, -1, 0);
System.out.println(count);
sc.close();
}
private static long dfs(int x, int y, int max, int cur) {
if(x >= n || y >= m){
return 0;
}
//因为到x,y这个坐标开始我们当前拿的数量肯定是零
int count = 0;
//先查询数组里面有没有这个值
if(rec[x][y][max + 1][cur] != -1){
return rec[x][y][max + 1][cur];
}
if(x == n - 1 && y == m - 1){
if(cur == k || (cur == k - 1 && arr[x][y] > max)){
count++;
}
return count;
}
if(arr[x][y] > max){
count += dfs(x + 1, y, arr[x][y], cur + 1);
count += dfs(x, y + 1, arr[x][y], cur + 1);
}
count += dfs(x + 1, y, max, cur);
count += dfs(x, y + 1, max, cur);
rec[x][y][max + 1][cur] = count % 1000000007;
return count % 1000000007;
}
}
非记忆性的深搜代码如下:
import java.util.Scanner;
public class Main {
static int arr[][];
static int count = 0;
static int m;
static int n;
static int k;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
k = sc.nextInt();
arr = new int[n][m];
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
arr[i][j] = sc.nextInt();
}
}
dfs(0, 0, -1, 0);
System.out.println(count);
sc.close();
}
private static void dfs(int x, int y, int max, int cur) {
if(x >= n || y >= m){
return;
if(x == n - 1 && y == m - 1){
if(cur == k || (cur == k - 1 && arr[x][y] > max)){
count++;
if(count > 1000000007){
count = count % 1000000007;
}
return;
}
}
if(arr[x][y] > max){
dfs(x + 1, y, arr[x][y], cur + 1);
dfs(x, y + 1, arr[x][y], cur + 1);
}
dfs(x + 1, y, max, cur);
dfs(x, y + 1, max, cur);
}
}