有一个骰子模拟器会每次投掷的时候生成一个 1 到 6 的随机数。
不过我们在使用它时有个约束,就是使得投掷骰子时, 连续 掷出数字 i
的次数不能超过 rollMax[i]
(i
从 1 开始编号)。
现在,给你一个整数数组 rollMax
和一个整数 n
,请你来计算掷 n
次骰子可得到的不同点数序列的数量。
假如两个序列中至少存在一个元素不同,就认为这两个序列是不同的。由于答案可能很大,所以请返回 模 10^9 + 7
之后的结果。
示例 1:
**输入:** n = 2, rollMax = [1,1,2,2,2,3]
**输出:** 34
**解释:** 我们掷 2 次骰子,如果没有约束的话,共有 6 * 6 = 36 种可能的组合。但是根据 rollMax 数组,数字 1 和 2 最多连续出现一次,所以不会出现序列 (1,1) 和 (2,2)。因此,最终答案是 36-2 = 34。
示例 2:
**输入:** n = 2, rollMax = [1,1,1,1,1,1]
**输出:** 30
示例 3:
**输入:** n = 3, rollMax = [1,1,1,2,2,3]
**输出:** 181
提示:
1 <= n <= 5000
rollMax.length == 6
1 <= rollMax[i] <= 15
方法一:动态规划
思路与算法
为了方便编写程序,我们重新定义骰子模拟器每次投掷生成的随机数为 0 \sim 5,且连续掷出数字 i 的次数不能超过 rollMax}[i],其中 i \in [0, 5]。
定义状态 d[i][j][k] 表示已经完成了 i 次掷骰子,第 i 次掷的是 j,并且已经连续掷了 k 次 j 的合法序列数。
初始状态从 i=1 开始,对于所有的 j \in [0, 5],令 d[1][j][1] = 1。
状态转移时,我们首先枚举表示上一次投掷的状态 d[i-1][j][k],然后再枚举当前这一次投掷生成的随机数 p,根据 p 是否等于 j 来分情况转移:
- 若 p \neq j,则令 d[i][p][1] 加上 d[i - 1][j][k]。
- 若 p = j,并且 k + 1 \le \textit{rollMax}[j],则令 d[i][p][k + 1] 加上 d[i - 1][j][k]。
最终,我们要求的答案是:
\sum_{j=0}^{5} \sum_{k=1}^{\textit{rollMax}[j]} d[n][j][k]
空间优化
注意到状态转移过程中,d[i] 这一层状态仅依赖 d[i - 1] 的状态,所以我们可以将 n 维状态表示压缩为 2 维,进一步优化空间。
具体的,我们令 t = i & 1(其中 & 表示按位与运算),则 d[t][j][k] 表示原 d[i][j][k] 状态, d[t \oplus 1][j][k] 表示原 d[i - 1][j][k] 状态(其中 \oplus 表示按位异或运算)。每次转移时,从 t \oplus 1 向 t 进行转移,注意转移之前需要将 d[t] 所有内容重置为 0。
代码
未使用空间优化的代码如下:
[sol1_1-C++]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class Solution { public: static constexpr int mod = 1E9 + 7; int dieSimulator(int n, vector<int>& rollMax) { vector d(n + 1, vector(6, vector<int>(16))); for (int j = 0; j < 6; j++) { d[1][j][1] = 1; } for (int i = 2; i <= n; i++) { for (int j = 0; j < 6; j++) { for (int k = 1; k <= rollMax[j]; k++) { for (int p = 0; p < 6; p++) { if (p != j) { d[i][p][1] = (d[i][p][1] + d[i - 1][j][k]) % mod; } else if (k + 1 <= rollMax[j]) { d[i][p][k + 1] = (d[i][p][k + 1] + d[i - 1][j][k]) % mod; } } } } } int res = 0; for (int j = 0; j < 6; j++) { for (int k = 1; k <= rollMax[j]; k++) { res = (res + d[n][j][k]) % mod; } } return res; } };
|
[sol1_1-Java]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class Solution { static final int MOD = 1000000007;
public int dieSimulator(int n, int[] rollMax) { int[][][] d = new int[n + 1][6][16]; for (int j = 0; j < 6; j++) { d[1][j][1] = 1; } for (int i = 2; i <= n; i++) { for (int j = 0; j < 6; j++) { for (int k = 1; k <= rollMax[j]; k++) { for (int p = 0; p < 6; p++) { if (p != j) { d[i][p][1] = (d[i][p][1] + d[i - 1][j][k]) % MOD; } else if (k + 1 <= rollMax[j]) { d[i][p][k + 1] = (d[i][p][k + 1] + d[i - 1][j][k]) % MOD; } } } } } int res = 0; for (int j = 0; j < 6; j++) { for (int k = 1; k <= rollMax[j]; k++) { res = (res + d[n][j][k]) % MOD; } } return res; } }
|
[sol1_1-C#]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public class Solution { const int MOD = 1000000007;
public int DieSimulator(int n, int[] rollMax) { int[][][] d = new int[n + 1][][]; for (int i = 0; i <= n; i++) { d[i] = new int[6][]; for (int j = 0; j < 6; j++) { d[i][j] = new int[16]; } } for (int j = 0; j < 6; j++) { d[1][j][1] = 1; } for (int i = 2; i <= n; i++) { for (int j = 0; j < 6; j++) { for (int k = 1; k <= rollMax[j]; k++) { for (int p = 0; p < 6; p++) { if (p != j) { d[i][p][1] = (d[i][p][1] + d[i - 1][j][k]) % MOD; } else if (k + 1 <= rollMax[j]) { d[i][p][k + 1] = (d[i][p][k + 1] + d[i - 1][j][k]) % MOD; } } } } } int res = 0; for (int j = 0; j < 6; j++) { for (int k = 1; k <= rollMax[j]; k++) { res = (res + d[n][j][k]) % MOD; } } return res; } }
|
[sol1_1-C]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| const int mod = 1E9 + 7;
int dieSimulator(int n, int* rollMax, int rollMaxSize) { int d[n + 1][6][16]; memset(d, 0, sizeof(d)); for (int j = 0; j < 6; j++) { d[1][j][1] = 1; } for (int i = 2; i <= n; i++) { for (int j = 0; j < 6; j++) { for (int k = 1; k <= rollMax[j]; k++) { for (int p = 0; p < 6; p++) { if (p != j) { d[i][p][1] = (d[i][p][1] + d[i - 1][j][k]) % mod; } else if (k + 1 <= rollMax[j]) { d[i][p][k + 1] = (d[i][p][k + 1] + d[i - 1][j][k]) % mod; } } } } } int res = 0; for (int j = 0; j < 6; j++) { for (int k = 1; k <= rollMax[j]; k++) { res = (res + d[n][j][k]) % mod; } } return res; }
|
[sol1_1-JavaScript]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| const MOD = 1000000007; var dieSimulator = function(n, rollMax) { const d = new Array(n + 1).fill(0).map(() => new Array(6).fill(0).map(() => new Array(16).fill(0))); for (let j = 0; j < 6; j++) { d[1][j][1] = 1; } for (let i = 2; i <= n; i++) { for (let j = 0; j < 6; j++) { for (let k = 1; k <= rollMax[j]; k++) { for (let p = 0; p < 6; p++) { if (p !== j) { d[i][p][1] = (d[i][p][1] + d[i - 1][j][k]) % MOD; } else if (k + 1 <= rollMax[j]) { d[i][p][k + 1] = (d[i][p][k + 1] + d[i - 1][j][k]) % MOD; } } } } }
let res = 0; for (let j = 0; j < 6; j++) { for (let k = 1; k <= rollMax[j]; k++) { res = (res + d[n][j][k]) % MOD; } } return res; };
|
使用空间优化的代码如下:
[sol1_2-C++]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| class Solution { public: static constexpr int mod = 1E9 + 7; int dieSimulator(int n, vector<int>& rollMax) { vector d(2, vector(6, vector<int>(16))); for (int j = 0; j < 6; j++) { d[1][j][1] = 1; } for (int i = 2; i <= n; i++) { int t = i & 1; for (int j = 0; j < 6; j++) { fill(d[t][j].begin(), d[t][j].end(), 0); } for (int j = 0; j < 6; j++) { for (int k = 1; k <= rollMax[j]; k++) { for (int p = 0; p < 6; p++) { if (p != j) { d[t][p][1] = (d[t][p][1] + d[t ^ 1][j][k]) % mod; } else if (k + 1 <= rollMax[j]) { d[t][p][k + 1] = (d[t][p][k + 1] + d[t ^ 1][j][k]) % mod; } } } } } int res = 0; for (int j = 0; j < 6; j++) { for (int k = 1; k <= rollMax[j]; k++) { res = (res + d[n & 1][j][k]) % mod; } } return res; } };
|
[sol1_2-Java]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| class Solution { static final int MOD = 1000000007;
public int dieSimulator(int n, int[] rollMax) { int[][][] d = new int[2][6][16]; for (int j = 0; j < 6; j++) { d[1][j][1] = 1; } for (int i = 2; i <= n; i++) { int t = i & 1; for (int j = 0; j < 6; j++) { Arrays.fill(d[t][j], 0); } for (int j = 0; j < 6; j++) { for (int k = 1; k <= rollMax[j]; k++) { for (int p = 0; p < 6; p++) { if (p != j) { d[t][p][1] = (d[t][p][1] + d[t ^ 1][j][k]) % MOD; } else if (k + 1 <= rollMax[j]) { d[t][p][k + 1] = (d[t][p][k + 1] + d[t ^ 1][j][k]) % MOD; } } } } } int res = 0; for (int j = 0; j < 6; j++) { for (int k = 1; k <= rollMax[j]; k++) { res = (res + d[n & 1][j][k]) % MOD; } } return res; } }
|
[sol1_2-C#]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public class Solution { const int MOD = 1000000007;
public int DieSimulator(int n, int[] rollMax) { int[][][] d = new int[2][][]; for (int i = 0; i < 2; i++) { d[i] = new int[6][]; for (int j = 0; j < 6; j++) { d[i][j] = new int[16]; } } for (int j = 0; j < 6; j++) { d[1][j][1] = 1; } for (int i = 2; i <= n; i++) { int t = i & 1; for (int j = 0; j < 6; j++) { Array.Fill(d[t][j], 0); } for (int j = 0; j < 6; j++) { for (int k = 1; k <= rollMax[j]; k++) { for (int p = 0; p < 6; p++) { if (p != j) { d[t][p][1] = (d[t][p][1] + d[t ^ 1][j][k]) % MOD; } else if (k + 1 <= rollMax[j]) { d[t][p][k + 1] = (d[t][p][k + 1] + d[t ^ 1][j][k]) % MOD; } } } } } int res = 0; for (int j = 0; j < 6; j++) { for (int k = 1; k <= rollMax[j]; k++) { res = (res + d[n & 1][j][k]) % MOD; } } return res; } }
|
[sol1_2-C]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| const int mod = 1E9 + 7;
int dieSimulator(int n, int* rollMax, int rollMaxSize) { int d[2][6][16]; memset(d, 0, sizeof(d)); for (int j = 0; j < 6; j++) { d[1][j][1] = 1; } for (int i = 2; i <= n; i++) { int t = i & 1; for (int j = 0; j < 6; j++) { memset(d[t][j], 0, sizeof(d[t][j])); } for (int j = 0; j < 6; j++) { for (int k = 1; k <= rollMax[j]; k++) { for (int p = 0; p < 6; p++) { if (p != j) { d[t][p][1] = (d[t][p][1] + d[t ^ 1][j][k]) % mod; } else if (k + 1 <= rollMax[j]) { d[t][p][k + 1] = (d[t][p][k + 1] + d[t ^ 1][j][k]) % mod; } } } } } int res = 0; for (int j = 0; j < 6; j++) { for (int k = 1; k <= rollMax[j]; k++) { res = (res + d[n & 1][j][k]) % mod; } } return res; }
|
[sol1_2-JavaScript]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| const MOD = 1000000007; var dieSimulator = function(n, rollMax) { const d = new Array(n + 1).fill(0).map(() => new Array(6).fill(0).map(() => new Array(16).fill(0))); for (let j = 0; j < 6; j++) { d[1][j][1] = 1; } for (let i = 2; i <= n; i++) { let t = i & 1; for (let j = 0; j < 6; j++) { d[t][j].fill(0); } for (let j = 0; j < 6; j++) { for (let k = 1; k <= rollMax[j]; k++) { for (let p = 0; p < 6; p++) { if (p !== j) { d[t][p][1] = (d[t][p][1] + d[t ^ 1][j][k]) % MOD; } else if (k + 1 <= rollMax[j]) { d[t][p][k + 1] = (d[t][p][k + 1] + d[t ^ 1][j][k]) % MOD; } } } } }
let res = 0; for (let j = 0; j < 6; j++) { for (let k = 1; k <= rollMax[j]; k++) { res = (res + d[n & 1][j][k]) % MOD; } } return res; };
|
复杂度分析
方法二:状态表示优化
思路与算法
定义状态 d[i][j] 表示已经完成了 i 次掷骰子,且第 i 次掷的是 j 时的合法序列数。那么有以下状态转移方程:
d[i][j] = \sum_{t=1}^{\textit{rollMax}[j]}\sum_{\substack{k = 0 \ k != j} }^5 d[i-t][k]
其具体含义是指,考虑从上一个不是 j 的位置 d[i - t] 来转移,可以覆盖到所有合法情况。此时,我们可以很容易的在 O(nm^2k) 的时间复杂度内求出答案。
若递推过程中顺便计算出每个 \sum\limits_{k=0}^5d[i][k],则可以将时间复杂度加快到 O(nmk)。
更进一步,我们发现在求解 d[i][j] 和 d[i - 1][j] 的过程中有很多重复运算:
d[i][j] = \sum_{\substack{k = 0 \ k != j} }^5(d[i - 1][k] + d[i - 2][k] + \cdots + d[i - \textit{rollMax}[j]][k])
d[i - 1][j] = \sum_{\substack{k = 0 \ k != j} }^5(d[i - 2][k] + d[i - 3][k] + \cdots + d[i - \textit{rollMax}[j] - 1][k]
因此,我们可以直接从 d[i - 1][j] 递推到 d[i][j]:
d[i][j] = \sum_{\substack{k = 0} }^5d[i - 1][k] - \sum_{\substack{k = 0 \ k != j} }^5d[i - \textit{rollMax}[j] - 1][k]
这个过程可以简单理解为求解长度是 rollMax}[j] 的一段区间和。过程中有三个边界情况需要处理:
- 若 i - \textit{rollMax}[j] - 1 < 0,我们取 0 作为转移点。
- 设 sum}[i] = \sum\limits_{j=0}^5 d[i][j],特殊的,我们令 sum}[0] = 1。
- 若 i \le \textit{rollMax}[j],我们需要额外给 d[i][j] 加 1。
代码
[sol2-C++]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Solution { public: static constexpr int mod = 1E9 + 7; int dieSimulator(int n, vector<int>& rollMax) { vector d(n + 1, vector<int>(6, 0)); vector<int> sum(n + 1, 0); sum[0] = 1; for (int i = 1; i <= n; i++) { for (int j = 0; j < 6; j++) { int pos = max(i - rollMax[j] - 1, 0); int sub = ((sum[pos] - d[pos][j]) % mod + mod) % mod; d[i][j] = ((sum[i - 1] - sub) % mod + mod) % mod; if (i <= rollMax[j]) { d[i][j] = (d[i][j] + 1) % mod; } sum[i] = (sum[i] + d[i][j]) % mod; } } return sum[n]; } };
|
[sol2-Java]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Solution { static final int MOD = 1000000007;
public int dieSimulator(int n, int[] rollMax) { int[][] d = new int[n + 1][6]; int[] sum = new int[n + 1]; sum[0] = 1; for (int i = 1; i <= n; i++) { for (int j = 0; j < 6; j++) { int pos = Math.max(i - rollMax[j] - 1, 0); int sub = ((sum[pos] - d[pos][j]) % MOD + MOD) % MOD; d[i][j] = ((sum[i - 1] - sub) % MOD + MOD) % MOD; if (i <= rollMax[j]) { d[i][j] = (d[i][j] + 1) % MOD; } sum[i] = (sum[i] + d[i][j]) % MOD; } } return sum[n]; } }
|
[sol2-C#]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class Solution { const int MOD = 1000000007;
public int DieSimulator(int n, int[] rollMax) { int[][] d = new int[n + 1][]; for (int i = 0; i <= n; i++) { d[i] = new int[6]; } int[] sum = new int[n + 1]; sum[0] = 1; for (int i = 1; i <= n; i++) { for (int j = 0; j < 6; j++) { int pos = Math.Max(i - rollMax[j] - 1, 0); int sub = ((sum[pos] - d[pos][j]) % MOD + MOD) % MOD; d[i][j] = ((sum[i - 1] - sub) % MOD + MOD) % MOD; if (i <= rollMax[j]) { d[i][j] = (d[i][j] + 1) % MOD; } sum[i] = (sum[i] + d[i][j]) % MOD; } } return sum[n]; } }
|
[sol2-C]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #define MAX(a, b) ((a) > (b) ? (a) : (b))
const int mod = 1000000007;
int dieSimulator(int n, int* rollMax, int rollMaxSize){ int d[n + 1][6]; int sum[n + 1]; memset(d, 0, sizeof(d)); memset(sum, 0, sizeof(sum)); sum[0] = 1; for (int i = 1; i <= n; i++) { for (int j = 0; j < 6; j++) { int pos = MAX(i - rollMax[j] - 1, 0); int sub = ((sum[pos] - d[pos][j]) % mod + mod) % mod; d[i][j] = ((sum[i - 1] - sub) % mod + mod) % mod; if (i <= rollMax[j]) { d[i][j] = (d[i][j] + 1) % mod; } sum[i] = (sum[i] + d[i][j]) % mod; } } return sum[n]; }
|
[sol2-JavaScript]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const MOD = 1000000007; var dieSimulator = function(n, rollMax) { const d = new Array(n + 1).fill(0).map(() => new Array(6).fill(0)); const sum = new Array(n + 1).fill(0); sum[0] = 1; for (let i = 1; i <= n; i++) { for (let j = 0; j < 6; j++) { let pos = Math.max(i - rollMax[j] - 1, 0); let sub = ((sum[pos] - d[pos][j]) % MOD + MOD) % MOD; d[i][j] = ((sum[i - 1] - sub) % MOD + MOD) % MOD; if (i <= rollMax[j]) { d[i][j] = (d[i][j] + 1) % MOD; } sum[i] = (sum[i] + d[i][j]) % MOD; } } return sum[n]; };
|
复杂度分析