地宫取宝——DFS记忆化搜索

[蓝桥杯 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(1n,m50,1k12)

接下来有 n n n 行数据,每行有 m m m 个整数 C i ( 0 ≤ C i ≤ 12 ) C_i(0 \le C_i \le 12) Ci(0Ci12) 代表这个格子上的宝物的价值。

输出格式

要求输出一个整数,表示正好取 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个变量:
当前坐标xy,以及当前捡到的宝物的最大价值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(0Ci12) ,现在的范围是 C i ( 1 ≤ C i ≤ 13 ) C_i(1 \le C_i \le 13) Ci(1Ci13)。原因在于,我们需要腾出一个状态用来表示起点位置(x=1,y=1)没有选择任何宝物,当前的选择的最大宝物的价值的无效值用max=0表示。避免状态转移出错。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值