标题:地宫取宝
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
资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 2000ms
请严格按要求输出,不要画蛇添足地打印类似:“请您输入...” 的多余内容。
所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
注意:不要使用package语句。不要使用jdk1.7及以上版本的特性。
注意:主类的名字必须是:Main,否则按无效代码处理。
首先看到这个矩阵型+只能向右/向下的搜索,首先想到类似“2013年振兴中华”这一题的dfs解法,那么就按照这一个思路去解决这个问题。
首先要有一个主函数,和一个dfs函数,dfs函数的参数很重要,分别设定为。x坐标,y坐标,max当前最大值,和cnt作为计算宝物数量的计数器变量,用来判断是否拿满了k件宝物。
当小明走到某一步的时候,如果当前取出的宝物比k大,就不在搜索这条分支,return 0;
dfs里面要注意的是,一定要有:
这样四种情况的递归调用。
当走到最右下角的时候,这时候有两种情况,一个是取满了k件宝物,可以return了,另外一种是还差一件取满k件宝物,并且最后一格上面放着的宝物比当前已有的max还大,就再取出一件宝物来。
*为了方便我没有输入数据,只是把样例数据写死在代码里面
private static final int MOD = 1000000007;
private static int n;
private static int m;
private static int k;
private static long ans = 0;
private static int[][] table = {
{1,2,3},{2,1,5}
};
public static void main(String[] args) {
//行
n=2;
//列
m=3;
//需要的宝物数量
k=2;
//缓存数组初始化为-1
ans = dfs(0,0,-1,0);
System.out.println(ans);
}
/**
* @param x
* @param y
* @param max
* @param cnt
* @return
*/
private static long dfs(int x,int y,int max,int cnt){
//溢出防御,递归出口
if(x==n||y==m||cnt>k){
return 0;
}
int value = table[x][y];
long ans = 0;
//走到最后一个格子前,递归出口
if(x==n-1&&y==m-1){
//一种情况是已经取满了k件宝物 另外一种情况是还差一件,但是最后一件的价值正好大于max
if(cnt==k||(cnt==k-1&&value>max)){
return 1;
}
return ans;
}
//如果当前格子的价值比最大值大,取出的情况
if(value>max){
//取出这个物品
ans+=dfs(x,y+1,value,cnt+1);
ans+=dfs(x+1,y,value,cnt+1);
}
//价值小或者价值大,但是不取出的情况
ans+=dfs(x,y+1,max,cnt);
ans+=dfs(x+1,y,max,cnt);
return ans%MOD;
}
上述代码都依照dfs深度搜索的基本题型进行书写即可,难点就在一跳出条件很复杂,递归的情况分支也很复杂。
并且这样做,递归的效率很低。当数据量比较大的时候,run程序的时间就比较长,这样给分就不高。
针对上面说的这种问题,我们观察题目,由于这个问题中,DFS函数的参数是有可能相同的。
比如中间的任意一个格子,可以是来自多个方向,并且从每个方向过来的小明,身上携带的宝物最大价值和宝物数量都有可能一样。
上面这种情况就是所谓的存在着重复子问题。这些具有相同参数的dfs函数在之前的代码里就需要反复的去计算,这对计算机的内存和运行时间都是极大的浪费。因此采用动态规划记忆型递归的方法来的方式来解决。
所谓记忆型递归就是吧相同参数的dfs函数的结果用一个长度为(i*j*n*m)的数组保存,ijnm分别是dfs参数可能取到的范围,比如题目中就有提示,这个记忆数组的长度就是51*51*14*14。当我们在这个数组中保存结果的时候,每一次调用dfs都从中查找相应的单元是否存在着之前记录下来的答案。如果有,就返回,不用多次的去调用求解。这样就节省了cpu资源和时间。
public class _地宫取宝记忆型递归 {
private static final int MOD = 1000000007;
private static int n;
private static int m;
private static int k;
private static long ans = 0;
//存储每次递归的结果
private static long[][][][] cache = new long[51][51][14][14];
private static int[][] table = {
{1,2,3},{2,1,5}
};
public static void main(String[] args) {
//行
n=2;
//列
m=3;
//需要的宝物数量
k=2;
//缓存数组初始化为-1
for (int i = 0; i <51 ; i++) {
for (int j = 0; j < 51; j++) {
for (int l = 0; l < 14; l++) {
for (int o = 0; o < 14; o++) {
cache[i][j][l][o] = -1;
}
}
}
}
ans = dfs(0,0,-1,0);
System.out.println(ans);
}
/**
* @param x
* @param y
* @param max
* @param cnt
* @return
*/
private static long dfs(int x,int y,int max,int cnt){
//查询缓存是否有记录,若有,返回记录
if(cache[x][y][max+1][cnt]!=-1){
return cache[x][y][max+1][cnt];
}
//溢出防御,递归出口
if(x==n||y==m||cnt>k){
return 0;
}
int value = table[x][y];
long ans = 0;
//走到最后一个格子前,递归出口
if(x==n-1&&y==m-1){
//一种情况是已经取满了k件宝物 另外一种情况是还差一件,但是最后一件的价值正好大于max
if(cnt==k||(cnt==k-1&&value>max)){
return 1;
}
return ans;
}
//如果当前格子的价值比最大值大,取出的情况
if(value>max){
//取出这个物品
ans+=dfs(x,y+1,value,cnt+1);
ans+=dfs(x+1,y,value,cnt+1);
}
//价值小或者价值大,但是不取出的情况
ans+=dfs(x,y+1,max,cnt);
ans+=dfs(x+1,y,max,cnt);
//写缓存
cache[x][y][value][cnt] = ans%MOD;
return ans%MOD;
}
}