题目描述
标题:地宫取宝
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 取模的结果。
输入:
输入一行3个整数,用空格分开:n m k (1<=n,m<=50, 1<=k<=12)
接下来有 n 行数据,每行有 m 个整数 Ci (0<=Ci<=12)代表这个格子上的宝物的价值
输出:
要求输出一个整数,表示正好取k个宝贝的行动方案数。该数字可能很大,输出它对 1000000007 取模的结果。
解题思路
很明显是一道深度优先搜索 + 递归实现题,看数据很大必须要进行记忆化搜索.。
安排一个四维数组data[x][y][max][count],来规划一个记忆型的空间。
意思是在走到x,y点的时候,手里已经有count件宝物,最大价值为max 到达终点的时候检查一下count是否等于k即可。
需要注意的是,递归起点不能从x=0,y=0,max=0,count=0开始,要从max=-1开始。因为一开始什么也没取并不存在max,但是数组又不存在下标-1,因此需要在以后的递归中把max+1即可。
代码示例
package problem.pro2014;
import java.util.Scanner;
public class Analyses_9地宫取宝 {
private static int[][] array;
private static int n;
private static int m;
private static int k;
private static int MOD = 1000000007;
private static long[][][][] data = new long[51][51][14][14];
public static void main(String[] args) {
// TODO Auto-generated method stub
@SuppressWarnings("resource")
Scanner input = new Scanner(System.in);
n = input.nextInt();
m = input.nextInt();
k = input.nextInt();
array = new int[n][m];
//随机生成一个n X m矩阵
for(int i = 0 ; i < array.length; i++) {
for(int j = 0; j < array[i].length; j++) {
//要求的是自己控制台输入
array[i][j] = input.nextInt();
}
}
//创建一个记忆型的动态规划空间
for(int i = 0; i < 51; i++) {
for(int j = 0; j < 51; j++) {
for(int a = 0; a < 14; a++) {
for(int b = 0; b < 14; b++) {
data[i][j][a][b] = -1;
}
}
}
}
//不能将参数传错
long answer = dfs(0,0,-1,0);
System.out.println(answer);
}
public static int dfs(int x,int y,int max,int count) {
//一个具有记忆型的动态规划
if(data[x][y][max + 1][count] != -1)return (int)data[x][y][max + 1][count];
if(x == n || y == m || count > k)return 0;
int current = array[x][y];
int ans = 0;
if(x == n - 1 && y == m - 1) {
if(count == k || (count == k -1 && current > max))
return 1;
return ans;
}
if(current > max) {
//宝物的价值大于他手中的价值,他拿起这件宝物的两种路径走法
ans += dfs(x + 1, y, current, count + 1);
ans += dfs(x, y + 1, current, count + 1);
}
//宝物的价值大于他手中的价值,他没有拿起这件宝物的两种路径走法
ans += dfs(x + 1, y, max, count);
ans += dfs(x, y + 1, max, count);
data[x][y][max + 1][count] = ans % MOD;
return ans;
}
}
错误分析(代码不是满分47分)
import java.util.Scanner;
public class Main {
//全局变量,为了方便后续方法的调用
private static int[][] array;
private static int n;
private static int m;
private static int k;
private static int MOD = 1000000007;
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner input = new Scanner(System.in);
n = input.nextInt();
m = input.nextInt();
k = input.nextInt();
array = new int[n][m];
//随机生成一个n X m矩阵
for(int i = 0 ; i < array.length; i++) {
for(int j = 0; j < array[i].length; j++) {
//要求的是自己控制台输入
array[i][j] = input.nextInt();
}
}
//不能将参数传错
long answer = dfs(0,0,-1,0);
System.out.println(answer);
}
public static int dfs(int x,int y,int max,int count) {
if(x == n || y == m || count > k)return 0;
//初始化变量
int current = array[x][y];
int ans = 0;
//边界条件
if(x == n - 1 && y == m - 1) {
if(count == k || (count == k -1 && current > max))
return 1;
return ans;
}
if(current > max) {
//宝物的价值大于他手中的价值,他拿起这件宝物的两种路径走法
ans += dfs(x + 1, y, current, count + 1);
ans += dfs(x, y + 1, current, count + 1);
}
//宝物的价值大于他手中的价值,他没有拿起这件宝物的两种路径走法
ans += dfs(x + 1, y, max, count);
ans += dfs(x, y + 1, max, count);
return ans % MOD;
}
}
分析:
(1)在主方法中,我们传参数的时候,max传入的值为 max = -1 ,在初始的条件下,将max设置为 max = -1 ,在递归的方法中,设置了一个变量 current 来和 max 进行比较,在题目中说到: “如果那个格子中的宝贝价值比小明手中任意宝贝价值都大,小明就可以拿起它(当然,也可以不拿)” ,我们就有两种前进的路径可选(一种是拿了价值更高的宝贝,另外一种是不拿价值更高的宝贝),所以有条件可知 1<=k<=12 所以我将创键一个四维的整型数组来开辟一个空间。
(2)四维数组的边界最大可以为:
for(int i = 0; i < 51; i++) {
for(int j = 0; j < 51; j++) {
for(int a = 0; a < 14; a++) {
for(int b = 0; b < 14; b++) {
data[i][j][a][b] = -1;
}
}
}
}
它的复杂度最大可以为: 51 X 51 X 14 X 14 ;
内存不会超过256M,这种方法虽然不是最优的解决方案,但是也满足了条件。
(3)不要忘了,将最后的结果对 1000000007 取模;
需要掌握的知识
递归:
程序编写递归的四条基本准则:
(1)基准情形:不需要递归就能解出数值;
(2)不断推进:适于需要递归求解的情形,每一次求解值都需要递归调用,朝基准情形靠拢;
(3)设计法则:也就是所有的递归调用都能运行;
(4)合成效益法则:求解同一问题应用时,不要做重复性的动作。
构成递归所具备的条件:
(1)子问题须与原始问题为同样的事,且更为简单;
(2)不能无限制地调用本身,须有个出口,化简为非递归状况处理。
递归的三要素:
(1)重复;
(2)变化;
(3)边界。
知识延伸
2013年蓝桥杯真题 —— 振兴中华
这一道真题和地宫取宝有异曲同工之处,可以研究一下,借用它的做题方法便可以做出地宫取宝这一道真题并且不考虑其他因素的前提下,可以得到42 分
package problem.pro2013;
/**
* 标题:振兴中华
*
* 小明参加了学校的趣味运动会,其中的一个项目是: 跳格子。
* 地上画着- -些格子,每个格子里写一一个字, 如下所示: (也可参见p1. jpg)l
*
* 从我做起振
* 我做起振兴
* 做起振兴中
* 起振兴中华
*
* 比赛时,先站在左上角的写着“从”字的格子里,可以横向或纵向跳到相邻的格子里,但不能跳到对角的格子或其它位置。
* 直要跳到“华”字结束。要求跳过的路线刚好构成“从我做起振兴中华”这句话。
*
* 请你帮助小明算-算他- 共有多少种可能的跳跃路线呢?
*
* 答案是一个整数,请提交这一个整数
* @author Lenovo
*
*/
public class Analyses3_振兴中华 {
public static void main(String[] args) {
// TODO Auto-generated method stub
int count = searchNumber(0,0);
System.out.println(count);
}
public static int searchNumber(int i,int j) {
if(i == 3 || j == 4)return 1;
return searchNumber(i+1,j) + searchNumber(i,j+1);
}
}
解法:
在这里我们使用深度搜索算法,每一次走的路线唯一, 在任意字上,在没有限制条件下,我们有四种走法,也 就是上下左右四种走法。
但是在这里我们只有两种走法,也就是要么向下,要 么向右这两种。故此我们借用坐标系来分析并且做这一题,能 够非常方便的帮助我们解决。
假如你正在玩这种游戏,你在坐标的原点,你只能向下和向右行走,边界是横坐标可以到达4,纵坐标可以到达3,你到达坐标(3,4)时表示结束。没遇到向下走或者向右走则变量count自增1。
这一道题:
递归思想的运用:重复,边界,变化。