n个m面的多面体骰子,求投出总数为s的所有可能情况有多少种 (LeetCode:1155. 掷骰子的N种方法)

如题。

(读题意,n个骰子,最小值为1,最大面值为m。 s也是有范围限制的)

 

思路:

采用分治思想。

首先判断边界,所有投掷的骰子的总数为s,s的范围是[n,n*m],n>0,m>0

需要确定的是,m是骰子的面数,这个值可以看作固定定值。

n和s是变量

 

先从最简单的开始,

先投一个骰子,n=1,得到的值为i,(1<= i <=m),

剩下n-1个骰子,剩下总数为s-i,

 

假设n个m面的多面体骰子投出总和s的可能情况种数为 R(n,m,s)

则Rn和R(n-1)的公式为:

其中最小结果值为R(1,m,s),这个容易计算。

可以用递归解决。(或者用动态规划解题)

 

举个例子:求R(3,4,6)

可能情况10种:【(1,1,4),(1,4,1),(4,1,1),
                              (1,2,3),(1,3,2),(2,1,3),(2,3,1),(3,1,2),(3,2,1),
                              (2,2,2)】

 

 

法一:(递归解决)

package com.xxxx;



/**
 * create by ziqiiii
 */
public class Example {


    static public void main(String[] args) {
        int result = foo(1, 1, 1);
        System.out.println(result);//可能情况1种:【1】

        result = foo(1, 2, 2);
        System.out.println(result);//可能情况1种:【2】

        result = foo(2, 1, 2);
        System.out.println(result);//可能情况1种:【1,1】

        result = foo(2, 2, 4);
        System.out.println(result);//可能情况1种:【2,2】

        result = foo(2, 3, 4);
        System.out.println(result);//可能情况3种:【(1,3),(2,2),(3,1)】

        result = foo(3, 4, 6);
        System.out.println(result);
        //可能情况10种:【(1,1,4),(1,4,1),(4,1,1),
        //              (1,2,3),(1,3,2),(2,1,3),(2,3,1),(3,1,2),(3,2,1),
        //              (2,2,2)】
    }


    static int foo(int n, int m, int s) {
        if (n <= 0 || m <= 0 || s < n || s > n * m) {
            return 0;
        }
        if (n == 1) { //返回条件
            return 1;
        }

        int sum = 0;
        for (int i = 1; i <= m; i++) {
    
      
            int res = foo(n - 1, m, s - i); //递归调用分支:骰子数n-1,总数s-i的情况
         
            sum = sum + res; //符合条件
        }
        return sum;
    }

}

 

 

法二:(动态规划)

凡是动态规划,划网格解决咯。(如贪心,01背包等问题)

 

 

开一个二维数组 res[][],将已经算过的值存起来。。

先要确定数组大小,这里我采用下标从1开始。

因为将m看作是固定值,将n看作自变量,s看作目标值。

其中n的范围为[1,n],s的范围为[1,s]

 

当n=1的所有值可以很容易确定

n>=2时,res[i][j]  的结果为 SUM:  res[i-1][ (j-1).....(j-m)]   

(即i的上一行从列下标为j-1到j-m的所有值的和,注意点是,要保证j-m>0不要越界了)

 

举例:R(3,4,6)

 //动态规划
    static int foo2(int n, int m, int s) {
        if (n <= 0 || m <= 0 || s < n || s > n * m) { //不符合的条件
            return 0;
        }

        //int[][] res = new int[n + 1][s + 1]; //开一个二维数组,下标开始为1

        int col = s > m ? s : m; //列数开大一点,直接取col=s的话,foo(1,6,3)就出错了
        int[][] res = new int[n + 1][col + 1]; //开一个二维数组,下标开始为1
        
        for (int i = 0; i <= n; i++) {
            for (int j = 0; j <= s; j++) {
                res[i][j] = 0;//初始化数组,全部值为0
            }
        }

        for (int j = 1; j <= m; j++) {
            res[1][j] = 1;  //当n=1时,容易知道最后的结果数
        }

        for (int i = 2; i <= n; i++) {

            for (int j = 2; j <= s; j++) {
                int num = 0;
                int min = j - m  > 1 ? (j - m ) : 1; //确定边界,最多取前一行第j列前面m个数值

                for (int k = j - 1; k >= min; k--) {
                    num = num + res[i - 1][k];
                     num %= 1000000007;  //思考:这里为什么取余数?
                }
                res[i][j] = num;
            }
        }


        //打印数组:
//        for (int i = 0; i <= n; i++) {
//            for (int j = 0; j <= s; j++) {
//                System.out.print(res[i][j]);
//                System.out.print(" ");
//            }
//            System.out.println();
//        }

        return res[n][s];
    }

 

输出结果为:

0 0 
0 1 
1
0 0 0 
0 1 1 
1
0 0 0 
0 1 0 
0 0 1 
1
0 0 0 0 0 
0 1 1 0 0 
0 0 1 2 1 
1
0 0 0 0 0 
0 1 1 1 0 
0 0 1 2 3 
3
0 0 0 0 0 0 0 
0 1 1 1 1 0 0 
0 0 1 2 3 4 3 
0 0 0 1 3 6 10 
10

 

==========(更新于:2019-08-16T16:11:41.113+08:00,跑数据开一下小差,毕竟这道题我耿耿于怀)==========

后来,我发现Leetcode有一道一摸一样的题:

1155. 掷骰子的N种方法

题目描述:

这里有 d 个一样的骰子,每个骰子上都有 f 个面,分别标号为 1, 2, ..., f。

我们约定:掷骰子的得到总点数为各骰子面朝上的数字的总和。

如果需要掷出的总点数为 target,

请你计算出有多少种不同的组合情况(所有的组合情况总共有 f^d 种),模 10^9 + 7 后返回。

 

示例 1:

输入:d = 1, f = 6, target = 3
输出:1

示例 2:输入:d = 2, f = 6, target = 7
输出:6

示例 3:输入:d = 2, f = 5, target = 10
输出:1

示例 4:输入:d = 1, f = 2, target = 3
输出:0

示例 5:输入:d = 30, f = 30, target = 500
输出:222616187
 

提示:

1 <= d, f <= 30
1 <= target <= 1000

 

提交递归版本是:【超出时间限制】,所以这道题不能用递归解决大的数据。

 

 

没有加  num %= 1000000007;  提交上去是:【解答错误】 

一看应该是int范围数据溢出了

 

为什么要加  %1000000007 ???

 

此处参考大神的题解

 

关于为什么取模 1e9 + 7
简单来说就是:

取模防止大数运算出现overflow
为什么取 1e9 + 7等prime number记为P, 可以减少实际答案A mapping到 A mod P的碰撞率。 我们约定认为, 如果你 A mod P如果是对的,那么你算出的A也是对的。
还有就是,对于参赛选手来说,这个数好记,不容易出现手误之类的;
更详细的digging看下面的链接:

Modulo 10^9+7 (1000000007)
Why “OUTPUT THE ANSWER MODULO 10^9 + 7"?
知乎 - 为什么很多程序竞赛题目都要求答案对 1e9+7 取模?

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值