1155. 擲骰子的N種方法
題目描述
這里有 d 個一樣的骰子,每個骰子上都有 f 個面,分別標號為 1, 2, ..., f。
我們約定:擲骰子的得到總點數為各骰子面朝上的數字的總和。
如果需要擲出的總點數為 target,請你計算出有多少種不同的組合情況(所有的組合情況總共有 f^d 種),模 10^9 + 7 后返回。
思路分析(二維)
這個題是動態規划問題,參考一篇文章吃透背包問題!(細致引入+解題模板+例題分析+代碼呈現)
的分析思路,首先,這個是一個分組背包問題,然后又是組合問題。分組背包的特點是在每個數的選擇上都有好幾個選擇,可以把每個篩子當成普通背包的重量,不過這個重量是看可以變得。可以用三個循環,前兩個循環按照題目按照0/1背包、完全背包和組合背包的要求寫,第三個循環時選擇重量(在本題中就是骰子的點數)。對於本題。最外面循環是骰子的個數d,然后 中間一層是target,由於這個每個骰子是不可重復用的,因此taget循環用倒序(如果是二維dp那么正序也可以),然后最后一層便是點數的循環f。本題中用dp[i] [j]表示用前i個骰子得到目標和j的組合數目,那么有dp[i] [j]+=dp[i] [k],k>=1&&k<=j
代碼實現
class Solution {
public:
int numRollsToTarget(int d, int f, int target) {
const int mod=1000000007;
vector<vector<int>>dp(d+1,vector<int>(target+1,0));
//初始化
dp[0][0]=1;
//對於骰子遍歷
for(int i=1;i<=d;i++)
{
//對於target的遍歷
for(int j=target;j>=1;j--)
{
//對於骰子的遍歷,細節1:j>=k
for(int k=1;k<=f&&j>=k;k++)
{
dp[i][j]+=dp[i-1][j-k];
dp[i][j]%=mod;
}
}
}
return dp[d][target];
}
};
思路分析(一維優化)
對於一維優化,設定dp[j]為目標和為j的組合數目。相比於二維,需要注意的一個細節便是骰子都必須用上,因此如果用假如兩個骰子那么得到的最小數便是2,那么就需要dp[j],j<i需要設置為0(二維dp不用擔心),另外一個細節是骰子不能搖出來0(所以不能用dp[j]直接加,需要從0開始加)。
class Solution {
public:
int numRollsToTarget(int d, int f, int target) {
const int mod=1000000007;
vector<int>dp(target+1,0);
dp[0]=1;
for(int i=1;i<=d;i++)
{
//細節1:倒序,這樣加的是上一層的
for(int j=target;j>=i;j--)
{
//細節2:只加當點數為1—k的
int sum=0;
for(int k=1;k<=f&&j>=k;k++)
{
sum+=dp[j-k];
sum%=mod;
}
dp[j]=sum;
}
//細節3:小於篩子個數的設置為0
for(int j=0;j<i;j++)
dp[j]=0;
}
return dp[target];
}
};