剑指 Offer 60. n个骰子的点数(概率DP)

博客介绍了如何解决概率动态规划问题,具体到n个骰子掷出的点数之和的所有可能性的概率计算。通过状态转移方程dp(i,j)=∑k=1661⋅dp(i−1,j−k)进行求解,并给出了基础情况和遍历策略。同时讨论了空间压缩和降维的优化技巧。" 133360722,19673915,Linux驱动:printk输出问题及解决策略,"['Linux', '内核开发', '驱动编程']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

剑指 Offer 60. n个骰子的点数

【概率DP】

n个骰子所能掷出的点数之和的所有可能性的概率。

解:

  • 方法:概率DP
    • i个骰子,那么它所能掷出的点数之和为i~6i
      • 掷i个骰子,掷出的点数之和为j,这是一种状态,记为(i,j)
        • 它是从掷了(i-1)个骰子的状态转移来的
          • 因为一个骰子的点数是1~6,所以掷了i个骰子,和为j(状态(i,j)),只可能是掷了(i-1)个骰子,和为(j-k)(k=1,…6)(即状态(i-1,j-k))这6个状态转移过来
            • 所以选择:6个
      • 有了状态,有了选择,写出状态转移方程:
        • 设dp(i,j)为掷i个骰子,掷出的点数之和为j的概率
          • 那么 d p ( i , j ) = ∑ k = 1 6 1 6 ⋅ d p ( i − 1 , j − k ) dp(i,j)= \sum_{k=1}^6\frac{1}{6}\cdot dp(i-1,j-k) dp(i,j)=k=1661dp(i1,jk)
          • base case: d p ( 1 , j ) = 1 6 dp(1, j) = \frac{1}{6} dp(1,j)=61
          • 要求的是 d p ( n , j ) dp(n, j) dp(n,j) (j=n,…6n)
        • 关于如何遍历:
          • 很显然,对于第i个骰子,有效的和是i~6i,<i和>6i的概率必为0,所以只关注i~6i即可
        • 其实也可以设dp(i,j)为掷i个骰子,掷出的点数之和为j的方法数
          • 则对于状态(n,j),概率为dp(i,j) / pow(6, n)
            • pow(6, n)为掷n个骰子的状态空间大小,即全部的方法数
// dp[i][j]:掷i个骰子,掷出的点数之和为j的概率
	vector<double> dicesProbability(int n) {
        vector<vector<double>> dp(n+1, vector<double>(6*n+1, 0.0)); //下标偏移1位
        for(int j = 1; j<= 6;j++) dp[1][j] = 1.0/6;
        for(int i = 2; i<= n; i++){
            for(int j = i; j <= 6*i; j++){
                for(int k = 1; k <= 6; k++){
                    if(j-k <= 0) break; //if(j-k <= 0 || j-k<i-1) break; 更严谨
                    dp[i][j] += 1.0/6*dp[i-1][j-k];
                }
            }
        }
        vector<double> res;
        for(int j = n; j<= 6*n; j++) res.push_back(dp[n][j]);
        return res;
    }

//dp[i,j]:掷i个骰子,掷出的点数之和为j的方法数
    vector<double> dicesProbability(int n) {
        vector<vector<double>> dp(n+1, vector<double>(6*n+1, 0.0)); //下标偏移1位
        for(int j = 1; j<= 6;j++) dp[1][j] = 1.0;
        for(int i = 2; i<= n; i++){
            for(int j = i; j <= 6*i; j++){
                for(int k = 1; k <= 6; k++){
                    if(j-k <= 0) break; //if(j-k <= 0 || j-k<i-1) break; 更严谨
                    dp[i][j] += dp[i-1][j-k];
                }
            }
        }
        vector<double> res;
        double cnt = pow(6.0, n); //n=6时状态空间总数
        for(int j = n; j<= 6*n; j++) res.push_back(dp[n][j]/cnt);
        return res;
    }

优化:空间压缩,降维

    vector<double> dicesProbability(int n) {
        vector<double> dp(6*n+1, 0.0); //下标偏移1位
        for(int j = 1; j<= 6;j++) dp[j] = 1.0/6;
        for(int i = 2; i<= n; i++){
            for(int j = 6*i; j >=i; j--){ //通过递推式(状态转移方程)得出要逆序遍历
                dp[j] = 0.0;
                for(int k = 1; k <= 6; k++){
                    if(j-k <= 0 || j-k<i-1) break; //必须大于(i-1)状态的对角线(即大于i-1)才行。因为状态压缩后,小于i-1的格子dp[k](k<i-1)里存的是状态(i-2)及之前的情况(遍历不到了所以没有更新),而状态未压缩时,dp[i-1][k](k<i-1)存的就是0,计算结果也符合真实情况
                    dp[j] += 1.0/6*dp[j-k];
                }
            }
        }
        vector<double> res;
        for(int j = n; j<= 6*n; j++) res.push_back(dp[j]);
        return res;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值