其他分享
首页 > 其他分享> > 背包问题之掷骰子的N种方法

背包问题之掷骰子的N种方法

作者:互联网

src=http___b-ssl.duitang.com_uploads_item_201503_20_20150320053810_Sz5nM.thumb.700_0.jpeg&refer=http___b-ssl.duitang.jpeg

背包问题之掷骰子的N种方法

方法1:朴素DP

分组背包的一道题,也有点类似求硬币组合数量那道题

定义 d p [ i ] [ j ] dp[i][j] dp[i][j]​​为在持有 i i i​个骰子的情况下,能组成 j j j​的点数的组合数

i i i的上限是 d d d即 d d d个骰子, j j j的上限是 t a r g e t target target​即目标的点数,初始化时new int[d+1][target+1],让下标从1开始

从边界条件考虑到一般条件, d p [ 0 ] [ 0 ] dp[0][0] dp[0][0]​​表示当前有0个骰子

考虑 d p [ i ] [ j ] dp[i][j] dp[i][j]​​​的一般情况,需要考虑其前序的 d p [ i − 1 ] [ j − k ] dp[i-1][j-k] dp[i−1][j−k]​​​的情况,对于第 i i i​​​个骰子,可以掷出 1... f 1...f 1...f​​​的点数

如上:当第 i i i个骰子晒出的点数比 j j j还要大的时候,已经没有意义了, j − f j-f j−f小于0的时候没有意义

        int MOD = (int) 1e9 + 7;

        /**
         * @param d d个一样的骰子
         * @param f 每个骰子上有f个面
         * @param t 目标的总点数
         * @return
         */
        public int numRollsToTarget(int d, int f, int t) {
            int[][] dp = new int[d + 1][t + 1];
            //初始化,0个骰子形成0的点数,只有一种组合数
            dp[0][0] = 1;
            //枚举i个骰子,上限是d个骰子
            for (int i = 1; i <= d; i++) {
                //枚举j的点数,上限是t的点数
                for (int j = 1; j <= t; j++) {
                    //开始枚举第i个骰子能掷出的点数k,范围是[1,f],j的点数小于k没有意义
                    for (int k = 1; k <= f && j >= k; k++) {
                        dp[i][j] = (dp[i][j] + dp[i - 1][j - k]) % MOD;
                    }
                }
            }
            //返回d个骰子掷出t的点数的方案数量
            return dp[d][t];
        }

方法2:空间优化DP

  int MOD = (int) 1e9 + 7;

        /**
         * @param d d个一样的骰子
         * @param f 每个骰子上有f个面
         * @param t 目标的总点数
         * @return
         */
        public int numRollsToTarget(int d, int f, int t) {
            int[] dp = new int[t + 1];
            //初始化,0个骰子形成0的点数,只有一种组合数
            dp[0] = 1;
            //枚举i个骰子,上限是d个骰子
            for (int i = 1; i <= d; i++) {
                //枚举j的点数,上限是t的点数 倒序遍历
                for (int j = t; j >= 0; j--) {
                    dp[j] = 0;//注意,每一轮遍历到j的时候,需要置为0
                    //开始枚举第i个骰子能掷出的点数k,范围是[1,f],j的点数小于k没有意义
                    for (int k = 1; k <= f && j >= k; k++) {
                        dp[j] = (dp[j] + dp[j - k]) % MOD;
                    }
                }
            }
            //返回d个骰子掷出t的点数的方案数量
            return dp[t];
        }

方法3:记忆化DFS

出口条件

 int MOD = (int) 1e9 + 7;
        Map<String, Integer> cache = new HashMap<>();

        /**
         * @param d      d个一样的骰子
         * @param f      每个骰子上有f个面
         * @param target 目标的总点数
         * @return
         */
        public int numRollsToTarget(int d, int f, int target) {
            if (d == 0 && target == 0) return 1;
            if (d == 0 || target == 0) return 0;
            //骰子数量+形成的点数
            String key = d + "#" + target;
            if (cache.containsKey(key)) return cache.get(key);
            int res = 0;//记录结果
            //当前能掷出的点数
            for (int i = 1; i <= f; i++) {
                //只有target的数量大于i才有意义
                if (target >= i) {
                    //用掉一个骰子,当前的骰子掷出来的点数是i,相应地,target数量也要减少
                    res = (res + numRollsToTarget(d - 1, f, target - i)) % MOD;
                } else {
                    break;
                }
            }
            //记忆化
            cache.put(key, res);
            return res;
        }

此处还可以有优化:

 int MOD = (int) 1e9 + 7;
        Map<String, Integer> cache = new HashMap<>();

        /**
         * @param d      d个一样的骰子
         * @param f      每个骰子上有f个面
         * @param target 目标的总点数
         * @return
         */
        public int numRollsToTarget(int d, int f, int target) {
            if (d == 0 && target == 0) return 1;
            //优化点:
            // 1.当前的骰子的数量为d,大于target,也就是d个骰子,每个骰子掷出1,都凑不出target
            // 2.当前的骰子的数量为d,每个骰子都能掷出f的点数,d*f<target 都凑不出target
            if (d > target || d * f < target) return 0;
            //骰子数量+形成的点数
            String key = d + "#" + target;
            if (cache.containsKey(key)) return cache.get(key);
            int res = 0;//记录结果
            //当前能掷出的点数
            for (int i = 1; i <= f; i++) {
                //只有target的数量大于i才有意义
                if (target >= i) {
                    //用掉一个骰子,当前的骰子掷出来的点数是i,相应地,target数量也要减少
                    res = (res + numRollsToTarget(d - 1, f, target - i)) % MOD;
                } else {
                    break;
                }
            }
            //记忆化
            cache.put(key, res);
            return res;
        }

标签:骰子,背包,return,target,int,掷骰子,点数,方法,dp
来源: https://blog.csdn.net/wat1r/article/details/121407533