问题描述
小U和小S正在玩一个有趣的骰子游戏。每个骰子都有固定数量的面数k ( 2 < = k < = 8 ) k(2<=k<=8)k(2<=k<=8),每一面的点数分别是1到k kk。 小U拥有n nn个骰子,每个骰子i的面数是a i a_ia i ,摇到每一面的概率均为1 / a i 1/a_i1/a i 。小S则有m mm个骰子,每个骰子j的面数是b j b_jb j ,摇到每一面的概率均为1 / b j 1/b_j1/b j 。 两人分别同时摇各自的骰子,并将摇出的点数相加,得分较高的一方获胜,得分相同则为平局。游戏只进行一次,没有重赛。现在小U想知道他获胜的概率是多少。你能帮他计算吗?
测试样例 样例1:
输入:n = 1, m = 3, arrayN = [8], arrayM = [2, 3, 4] 输出:0.255
样例2:
输入:n = 2, m = 2, arrayN = [3, 4], arrayM = [3, 3] 输出:0.5
样例3:
输入:n = 3, m = 1, arrayN = [2, 2, 2], arrayM = [4] 输出:0.844
解题思路
两个人各自n nn个骰子,每个骰子面还不一样,让我们求前者骰子和更大的概率是多少?比较流畅的思路就是单独求他们各自所有可能出现的点数对应的概率,然后呢再逐次比较获得结果。比较应该是好比较的,两个forfor嵌套就可以出来,这道题的关键就在于我们如何把单独一个人所有可能点数对应的概率求出来: 我们以三个骰子,面数分别为2 、 3 、 4 2、3、42、3、4为例:第一步:初始化概率数组dp
第二步:处理第一个骰子(2面) 新建一个数组newDp为 [ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [0,0,0,0,0,0,0,0,0,0] 长度依然为10。 此时我们要遍历所有可能的点数,也就是从 0 到 9 从0到9从0到9 每次呢检查概率数组dp中对应点数和的概率,当某个点数的概率不是0时,进行概率更新
第三步:处理第二个骰子(3面) 新建一个数组newDp为 [ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [0,0,0,0,0,0,0,0,0,0] 此时仍然是遍历所有可能的点数,从 0 到 9 从0到9从0到9 概率数组dp中对应的概率和不是0时,对所在位置概率进行更新
第四步:处理第三个骰子(4面) 新建一个数组newDp为 [ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [0,0,0,0,0,0,0,0,0,0] 遍历所有可能的点数,从 0 到 9 从0到9从0到9 概率数组dp中对应的概率和不是0时,对所在位置概率进行更新
第五步:传出最终结果
算法思路
我们要用两个数组,一个概率数组,一个每一轮的数组。什么意思呢?我们可以把三次骰子看做是逐次抛出:
第一轮,每轮数组会存入两面骰子可能出现的点数及其对应的概率,也就是下标为1、2的位置更新概率;(但我们犹记刚才这两个数组的长度都是2+3+4+1,所以其余位置就都保留为0),然后概率数组复制结果;
第二轮,每轮数组首先都归零,然后遍历概率数组,对概率不是0的点呢进行本轮的概率更新,把结果都存入新的每轮数组中,保证了此时每次存入每轮数组中的概率都是两个骰子相乘的结果,然后概率数组复制结果;
第三轮呢,同第二轮,也保证了每次存入新的每轮数组中的结果都是三个骰子相乘的结果,然后概率数组复制结果,得到最终结果。
算法解题思路扩展
暴力枚举法
对于简单的情况,可以直接枚举所有可能的骰子结果,然后统计满足胜利条件的结果数量。但这种方法在骰子数量较多时效率极低,因为时间复杂度呈指数级增长。例如,n 个骰子,每个骰子 6 种情况,总共有 6^n 种可能性。
动态规划法(对于部分问题)
在一些点数和相关的问题中,可以利用动态规划来降低计算复杂度。比如计算多个骰子的点数和问题,可以通过记录已经计算过的骰子数量和点数和的情况,避免重复计算,将时间复杂度从指数级降低到多项式级别。
数学公式法
如果能推导出相关的数学公式,那是最理想的。比如对于两个骰子点数和的分布,可以利用概率论中的公式来计算,这需要对概率分布函数等知识有深入理解。
学习心得
在实现算法后,需要通过大量的测试用例来验证。可以从简单情况开始,如两个骰子的情况,逐步增加骰子数量和复杂度。如果发现算法效率低下或者结果错误,要仔细分析是算法思路问题还是代码实现问题。对于效率问题,可以考虑优化算法,如采用更合适的数据结构来存储中间结果,或者进一步挖掘数学规律来简化计算。通过不断地调试和优化,才能更好地掌握摇骰子胜利概率算法问题的求解方法。
代码如下
import java.util.Arrays;
public class Main {
public static double solution(int n, int m, int[] arrayN, int[] arrayM) {
double[] probA = calculateProbabilities(arrayN);
double[] probB = calculateProbabilities(arrayM);
double winProbability = 0.0;
for (int i = 0; i < probA.length; i++) {
for (int j = 0; j < probB.length; j++) {
if (i > j) {
winProbability += probA[i] * probB[j];
}
}
}
// 保留三位小数
return Math.round(winProbability * 1000.0) / 1000.0;
}
private static double[] calculateProbabilities(int[] dice) {
int maxSum = Arrays.stream(dice).sum();
double[] dp = new double[maxSum + 1];
dp[0] = 1.0;
for (int die : dice) {
double[] newDp = new double[maxSum + 1];
for (int sum = 0; sum <= maxSum; sum++) {
if (dp[sum] > 0) {
for (int face = 1; face <= die; face++) {
newDp[sum + face] += dp[sum] / die;
}
}
}
dp = newDp;
}
return dp;
}
public static void main(String[] args) {
System.out.println(solution(1, 3, new int[]{8}, new int[]{2, 3, 4}));
}
}