骰子胜利概率问题:动态规划 | 豆包MarsCode AI刷题

208 阅读4分钟

分析摇骰子胜利概率问题:动态规划的深度解析

题目背景

摇骰子是一个经典的概率问题,计算在不同条件下获胜的概率不仅是一个有趣的数学问题,也有许多现实应用。本文基于一个具体问题进行分析:两个人分别掷骰子,最终比较点数和,求小U获胜的概率。

题目规则与输入输出

  • 每个骰子有固定面数,掷出每一面的概率均等。
  • 小U和小S分别有自己的骰子集合,点数和较高的一方获胜。
  • 输出小U获胜的概率,结果保留三位小数。

例如,给定输入 n=1, m=3, arrayN=[8], arrayM=[2, 3, 4],表示小U有一个8面的骰子,小S有三个骰子,面数分别为2、3、4。计算小U获胜的概率。


解题思路

本题核心在于求解两个玩家点数和的分布,以及基于分布计算获胜概率。以下是详细步骤:

  1. 点数和分布的计算:

    • 假设一个玩家有若干骰子,每个骰子有不同的面数。我们需要计算每种点数和的概率分布。
    • 使用动态规划数组 dp,其中 dp[i] 表示点数和为 i 的概率。
  2. 动态规划计算点数分布:

    • 初始状态:dp[0] = 1 表示初始点数和为0的概率为1。
    • 遍历每个骰子,更新点数分布。对于每个骰子面数 k,依次增加 1k,累积概率。
  3. 计算胜利概率:

    • 小U获胜的概率需要比较点数和分布。对于小U点数和 sumU,小S所有小于 sumU 的点数概率加和,即为小U获胜的概率。
    • 最终的概率由所有 sumU 可能值对应的胜率累积而得。

代码实现与分析

以下是基于上述思路的代码实现:

import java.util.Arrays;

public class Main {

    public static double solution(int n, int m, int[] arrayN, int[] arrayM) {
        // 计算小U和小S的最大点数和
        int maxSumU = Arrays.stream(arrayN).sum() * 8;
        int maxSumS = Arrays.stream(arrayM).sum() * 8;

        // 初始化动态规划数组
        double[] dpU = new double[maxSumU + 1];
        double[] dpS = new double[maxSumS + 1];

        dpU[0] = 1.0;  // 初始分布
        for (int sides : arrayN) {
            double[] nextDpU = new double[maxSumU + 1];
            for (int i = 0; i <= maxSumU; i++) {
                if (dpU[i] > 0) {
                    for (int face = 1; face <= sides; face++) {
                        nextDpU[i + face] += dpU[i] / sides;
                    }
                }
            }
            dpU = nextDpU;
        }

        dpS[0] = 1.0;  // 初始分布
        for (int sides : arrayM) {
            double[] nextDpS = new double[maxSumS + 1];
            for (int i = 0; i <= maxSumS; i++) {
                if (dpS[i] > 0) {
                    for (int face = 1; face <= sides; face++) {
                        nextDpS[i + face] += dpS[i] / sides;
                    }
                }
            }
            dpS = nextDpS;
        }

        // 计算胜利概率
        double winProbability = 0.0;
        for (int sumU = 0; sumU <= maxSumU; sumU++) {
            double sumSProb = 0.0;
            for (int sumS = 0; sumS < sumU && sumS <= maxSumS; sumS++) {
                sumSProb += dpS[sumS];
            }
            winProbability += dpU[sumU] * sumSProb;
        }

        // 返回结果,保留三位小数
        return Math.round(winProbability * 1000.0) / 1000.0;
    }

    public static void main(String[] args) {
        System.out.println(solution(1, 3, new int[]{8}, new int[]{2, 3, 4}));  // 输出:0.255
        System.out.println(solution(2, 2, new int[]{3, 4}, new int[]{3, 3}));  // 输出:0.500
        System.out.println(solution(3, 1, new int[]{2, 2, 2}, new int[]{4}));  // 输出:0.844
    }
}

代码分析

  1. 点数分布计算

    • dpUdpS 分别表示小U和小S的点数分布。
    • 对每个骰子更新点数概率时,采用二维数组模拟累积,从而避免冗余计算。
  2. 胜利概率累积

    • 使用两层嵌套循环计算胜率,对于小U每个可能点数 sumU,枚举小S所有小于 sumU 的点数 sumS
  3. 优化空间复杂度

    • 动态规划数组在每轮循环结束后更新,因此使用 nextDp 临时变量避免额外空间消耗。

测试用例解析

测试用例1

  • 输入:n=1, m=3, arrayN=[8], arrayM=[2,3,4]

  • 分析:

    • 小U最大点数和为 8,小S最大点数和为 9
    • 小U的点数分布为均匀分布,小S分布较广。
    • 最终计算得小U获胜概率为 0.255

测试用例2

  • 输入:n=2, m=2, arrayN=[3,4], arrayM=[3,3]

  • 分析:

    • 小U和小S点数和分布相近,均匀分布导致胜率平衡。
    • 最终获胜概率为 0.500

测试用例3

  • 输入:n=3, m=1, arrayN=[2,2,2], arrayM=[4]

  • 分析:

    • 小U点数和集中在较高范围。
    • 小S获胜概率低,小U获胜概率达 0.844

个人思考与扩展

这道题目展示了动态规划在概率分布计算中的强大应用。同时,通过优化动态规划数组的空间使用,提升了算法性能。

扩展场景

  1. 在多人游戏中,可扩展动态规划数组的维度以处理多方点数分布。
  2. 增加规则,例如骰子结果相加带权重,进一步复杂化算法。

通过深入理解动态规划,我们可以更高效地解决类似问题,同时为更多复杂概率场景提供解决思路。