分析摇骰子胜利概率问题:动态规划的深度解析
题目背景
摇骰子是一个经典的概率问题,计算在不同条件下获胜的概率不仅是一个有趣的数学问题,也有许多现实应用。本文基于一个具体问题进行分析:两个人分别掷骰子,最终比较点数和,求小U获胜的概率。
题目规则与输入输出
- 每个骰子有固定面数,掷出每一面的概率均等。
- 小U和小S分别有自己的骰子集合,点数和较高的一方获胜。
- 输出小U获胜的概率,结果保留三位小数。
例如,给定输入 n=1, m=3, arrayN=[8], arrayM=[2, 3, 4],表示小U有一个8面的骰子,小S有三个骰子,面数分别为2、3、4。计算小U获胜的概率。
解题思路
本题核心在于求解两个玩家点数和的分布,以及基于分布计算获胜概率。以下是详细步骤:
-
点数和分布的计算:
- 假设一个玩家有若干骰子,每个骰子有不同的面数。我们需要计算每种点数和的概率分布。
- 使用动态规划数组
dp,其中dp[i]表示点数和为i的概率。
-
动态规划计算点数分布:
- 初始状态:
dp[0] = 1表示初始点数和为0的概率为1。 - 遍历每个骰子,更新点数分布。对于每个骰子面数
k,依次增加1到k,累积概率。
- 初始状态:
-
计算胜利概率:
- 小U获胜的概率需要比较点数和分布。对于小U点数和
sumU,小S所有小于sumU的点数概率加和,即为小U获胜的概率。 - 最终的概率由所有
sumU可能值对应的胜率累积而得。
- 小U获胜的概率需要比较点数和分布。对于小U点数和
代码实现与分析
以下是基于上述思路的代码实现:
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
}
}
代码分析
-
点数分布计算:
dpU和dpS分别表示小U和小S的点数分布。- 对每个骰子更新点数概率时,采用二维数组模拟累积,从而避免冗余计算。
-
胜利概率累积:
- 使用两层嵌套循环计算胜率,对于小U每个可能点数
sumU,枚举小S所有小于sumU的点数sumS。
- 使用两层嵌套循环计算胜率,对于小U每个可能点数
-
优化空间复杂度:
- 动态规划数组在每轮循环结束后更新,因此使用
nextDp临时变量避免额外空间消耗。
- 动态规划数组在每轮循环结束后更新,因此使用
测试用例解析
测试用例1:
-
输入:
n=1, m=3, arrayN=[8], arrayM=[2,3,4] -
分析:
- 小U最大点数和为
8,小S最大点数和为9。 - 小U的点数分布为均匀分布,小S分布较广。
- 最终计算得小U获胜概率为
0.255。
- 小U最大点数和为
测试用例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。
个人思考与扩展
这道题目展示了动态规划在概率分布计算中的强大应用。同时,通过优化动态规划数组的空间使用,提升了算法性能。
扩展场景:
- 在多人游戏中,可扩展动态规划数组的维度以处理多方点数分布。
- 增加规则,例如骰子结果相加带权重,进一步复杂化算法。
通过深入理解动态规划,我们可以更高效地解决类似问题,同时为更多复杂概率场景提供解决思路。