如题。
(读题意,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有一道一摸一样的题:
题目描述:
这里有 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 取模?