骰子的点数
题目描述
将一个骰子投掷 n
次,获得的总点数为 s
,s
的可能范围为 n∼6n
。
掷出某一点数,可能有多种掷法,例如投掷 2
次,掷出 3
点,共有 [1,2],[2,1]
两种掷法。
请求出投掷 n次,掷出 n∼6n点分别有多少种掷法。
数据范围:1≤n≤10
样例1
输入:n=1
输出:[1, 1, 1, 1, 1, 1]
解释:投掷1次,可能出现的点数为1-6,共计6种。每种点数都只有1种掷法。所以输出[1, 1, 1, 1, 1, 1]。
样例2
输入:n=2
输出:[1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1]
解释:投掷2次,可能出现的点数为2-12,共计11种。每种点数可能掷法数目分别为1,2,3,4,5,6,5,4,3,2,1。
所以输出[1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1]。
思路
DP分析主要流程:
- 状态表示
- 状态计算
状态表示:按照题目来, 本题要求投掷n次,掷出n~6n点分别有多少种掷法。因此是两维状态,f[i][j]表示投掷i次,掷出j点数有多少种掷法。
状态计算:这个地方有点复杂, 主要是寻找变化和不变化的部分
现在思考这么个场景
有一个问卷调查, 一共n道题目每个题目都是判断题, 你只需要填true或false
如果直接求会有些复杂(假设你不会公式)
那么你需要敢肯定的是:
- 乘法原理, 每次的结果相乘即可
- 并且最后一次不是true就是false
那么你就知道f[n] = true或者f[n] = false;
而中间的某次, 肯定也是这样, 也就是f[i] = true或者f[i] = false;
如果你能想明白,恭喜你已经会本题了
显然,每次扔骰子,只有1~6种方案,上面的例子是乘法原理,本题求的是方案数,是加法原理
并且,投n次,总和为s
那么中间的某次肯定是投了n - i次,总和为6 * n - k
因此,状态计算就是把这些中间状态利用加法原理进行累加即可
代码
i:投了多少次
j: 投n次的情况下,总和是多少
k: 每次的点数是多少
class Solution {
public:
vector<int> numberOfDice(int n) {
vector<vector<int>> f(n + 1, vector<int>(n * 6 + 1));
f[0][0] = 1;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= i * 6; j ++ )
for (int k = 1; k <= min(j, 6); k ++ ) // 注意投的点数要小于总和
f[i][j] += f[i - 1][j - k];
vector<int> res;
for (int i = n; i <= n * 6; i ++ )
res.push_back(f[n][i]);
return res;
}
};
易错点
- 第一层循环为枚举投的次数,第二层循环为每次总和,因此j的哨兵为:j <= i * 6
- 状态转移: 最后一次投了k, 那么之前投的状态为:f[i - 1][j - k],表示投的次数少了一次,并且总和减少了k
- 答案循环,答案需要投N次的总和,因此f的第一维固定为n,第二位是总和,因为投了n次,所以总和为n ~ n * 6