题目解析:摇骰子的胜利概率 | 豆包MarsCode AI刷题

421 阅读4分钟

一、问题分析

小U和小S正在玩一个骰子游戏,双方拥有不同数量的骰子,每个骰子的面数可能不同。每个人在游戏中同时摇动各自的骰子,并将摇出的点数相加,得分较高的一方获胜,若两人的得分相同,则判为平局。目标是计算小U获胜的概率。要解决这个问题,关键在于计算小U和小S可能的得分分布。通过得分分布,可以得到双方得分的概率,从而计算出小U获胜的概率。

二、思路解析

  1. 概率分布计算
    首先,我们需要确定小U和小S每次游戏可能获得的总得分及其概率。由于每个骰子各个面上的点数出现概率是均等的,因此,可以使用动态规划的方式来计算多个骰子组合出的总得分及其概率分布。对于小U的每个得分,计算出在该分数下小U胜出的概率。

    具体地,对于每一个骰子,掷出每个点数的概率相同。若掷多个骰子,则每个可能的组合结果对应的概率是各骰子结果概率的乘积。我们需要累加不同组合的结果以得到每一个得分的最终概率。

  2. 动态规划计算概率分布
    动态规划是实现概率分布的理想方法。为此,我们使用一个字典 dp 来存储不同得分的出现次数。初始时,dp 设为 {0: 1},即未掷骰子时总得分为零的唯一组合。

    每个骰子的面数会依次累加到 dp 中:对于骰子的每一个面,当前得分会增加相应的面值,并将该结果累积到新的得分表 new_dp 中。这个过程会重复,直到所有骰子都被加入到得分统计中。

    最后,需要对每一个得分的出现次数进行归一化,即将每个得分的频次除以总的组合数,得到每个得分出现的概率。此时,dp 就成为最终的得分概率分布表,其中每个得分与其概率成对存储。

  3. 胜率计算
    有了小U和小S的得分概率分布后,可以计算小U的胜率。具体步骤如下:

    • 遍历小U的每一个得分情况 u_sum,该得分出现在小U的概率为 u_prob
    • 对于每一个 u_sum,计算小S的所有得分小于 u_sum 的概率之和 s_lose_probability
    • 小U以 u_sum 得分获胜的概率为 u_prob * s_lose_probability,将其累加到总胜率上。

    这一方法确保了我们考虑到所有可能得分情况及其对应的概率,准确计算小U胜出的概率。

三、代码详解

代码通过两个主要函数实现,分别计算概率分布和胜率。

  1. calculate_probability_distribution
    该函数使用动态规划方法来计算得分概率分布。dp 初始值为 {0: 1},表示未掷骰子时唯一的组合得分情况。然后,对于每一个骰子,逐一更新可能得分的出现次数。将面数范围内的每个可能值累加到当前得分上,并记录新的得分组合情况。

    当所有骰子计算完成后,计算总的组合数,对 dp 中的每个得分出现次数除以组合总数,从而得到该得分的概率分布。

  2. solution
    该函数负责计算小U的胜率。首先分别调用 calculate_probability_distribution 函数来得到小U和小S的得分概率分布。然后,遍历小U的得分情况 u_sum,对于每一种可能的得分,计算小S小于 u_sum 的概率和,并将其乘以 u_sum 的出现概率 u_prob。最终结果累加以得到小U的总胜率。

四、代码实现

from collections import defaultdict
from itertools import product

def calculate_probability_distribution(dice_faces):
    dp = defaultdict(int)
    dp[0] = 1

    # 动态规划计算得分概率分布
    for faces in dice_faces:
        new_dp = defaultdict(int)
        for current_sum, count in dp.items():
            for outcome in range(1, faces + 1):
                new_dp[current_sum + outcome] += count
        dp = new_dp

    # 归一化每个得分的概率
    total_outcomes = sum(dp.values())
    for key in dp:
        dp[key] /= total_outcomes
    
    return dp

def solution(n, m, arrayN, arrayM):
    # 小U和小S的得分概率分布
    u_distribution = calculate_probability_distribution(arrayN)
    s_distribution = calculate_probability_distribution(arrayM)

    u_win_probability = 0.0
    
    # 计算小U的胜率
    for u_sum, u_prob in u_distribution.items():
        s_lose_probability = sum(s_prob for s_sum, s_prob in s_distribution.items() if u_sum > s_sum)
        u_win_probability += u_prob * s_lose_probability

    return round(u_win_probability, 3)

# 测试用例
if __name__ == "__main__":
    # 测试输出应为True
    print(solution(1, 3, [8], [2, 3, 4]) == 0.255)
    print(solution(2, 2, [3, 4], [3, 3]) == 0.5)
    print(solution(3, 1, [2, 2, 2], [4]) == 0.844)

通过以上算法,我们计算了小U的胜率,并验证了算法的准确性。在测试用例中,小U的胜率分别计算为 0.2550.50.844,符合预期的正确答案。这表明该算法能够有效计算出骰子游戏中小U的胜率。