AcWing 80. 骰子的点数 -DP

题目
将一个骰子投掷n次,获得的总点数为s,s的可能范围为n~6n。
掷出某一点数,可能有多种掷法,例如投掷2次,掷出3点,共有[1,2],[2,1]两种掷法。

请求出投掷n次,掷出n~6n点分别有多少种掷法。

样例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]。

思路:
1、设计dp状态
题目要求的是一个数组,数组的下标表示掷出的点数,值表示方法的种数,可以联想到这其实是dp数组的一部分。
因此描述一个状态的一个参数是当前掷出的点数。但这一个参数不够确定一个状态,因此再寻找有什么变量能决定状态,发现使用的骰子的个数可以作为另一个参数。
因此,令dp[i][j]表示用i个骰子扔出和为 j 的方法数。

2、求状态转移方程
某一状态与前一状态的区别在于最后一个新增的骰子掷出的点数。
于是可以得到状态转移方程:dp[i][j] = dp[i-1][1]+dp[i-1][2]+…+dp[i-1][6]

3、对一些状态初始化
1个骰子掷出点数为x的方法数肯定只有一种啦,所以

for(int i = 1; i <= 6; ++i)
	dp[1][i] = 1;

4、确定递推的顺序
递推是从已知推未知,所以要保证参与更新当前状态的状态值,都是求出且正确的,根据就是状态转移方程。求dp[i][j]时,i显然是从小到大遍历,但 j 必须从大到小遍历,因为假设最后一个骰子点数是k,则

for(int k = 1; k <= 6; ++k)//   最后一个骰子的可能值
	if(j - k >= 0)   //  
		dp[i][j] += dp[i-1][j-k];

可以看出求状态 j 需要用到 j 左边的状态,所以 j 要从右边开始推,并且由于i表示骰子个数,j的范围是[i, 6 * i]

下面贴上二维版dp的AC代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;

class Solution {
public:
    vector<int> numberOfDice(int n) {
        vector<int> dp[n+1];
        for(int i = 0; i <= n; ++i)
            dp[i].resize(6*n+1, 0);
        for(int i = 1; i <= 6; ++i)
            dp[1][i] = 1;
        for(int i = 2; i <= n; ++i){
            for(int j = 6*i; j >= i; --j){//j要从大到小推
                dp[i][j] = 0;  
                for(int k = 1; k <= 6; ++k)//   最后一个骰子的可能值
                    if(j - k >= 0)  
                        dp[i][j] += dp[i-1][j-k];
            }
        }
        vector<int> res(dp[n].begin()+n, dp[n].end());
        return res;
    }
};

int main(){
    Solution s;
    vector<int> v;
    int n;
    cin >> n;
    v = s.numberOfDice(n);
    for(int x : v)
        cout << x << ' ';

    return 0;
}
/*
3 3
2 3 1
1 7 1
4 6 1
*/

由于求当前状态只需要上一层的状态,因此可以降维,转移方程变为dp[j] += dp[j-k],但注意! 此时 j 的递推范围改变了,用i个骰子,最多扔出6*i,所以 j 最大取从6 * i;最少扔出i,但由于是滚动数组,两个层的状态共用一维,上一次的0~i可能有了值,所以要重新覆盖成0,j最小取1,而不是i。(否则dp值变大,多加了)

附上降维的代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;

class Solution {
public:
    vector<int> numberOfDice(int n) {
        vector<int> dp(n*6+1, 0);
        for(int i = 1; i <= 6; ++i)
            dp[i] = 1; 
        for(int i = 2; i <= n; ++i){
            for(int j = 6*i; j >= 1; --j){
            //用i个骰子,最多扔出6*i,所以j最大取从6*i;最少扔出i,但由于是滚动数组,
            //上一次的0~i可能有了值,所以要重新覆盖成0,j最小取1
                dp[j] = 0;  
                for(int k = 1; k <= 6; ++k)
                    if(j - k >= 0)   
                        dp[j] += dp[j-k];
            }
        }
        vector<int> res(dp.begin()+n, dp.end());
        return res;
    }
};

int main(){
    Solution s;
    vector<int> v;
    int n;
    cin >> n;
    v = s.numberOfDice(n);
    for(int x : v)
        cout << x << ' ';

    return 0;
}
/*
3 3
2 3 1
1 7 1
4 6 1
*/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值