分汤

148 阅读1分钟

分汤

题目

原题链接:分汤

有 A 和 B 两种类型 的汤。一开始每种类型的汤有 n 毫升。有四种分配操作:

  1. 提供 100ml 的 汤A 和 0ml 的 汤B 。
  2. 提供 75ml 的 汤A 和 25ml 的 汤B 。
  3. 提供 50ml 的 汤A 和 50ml 的 汤B 。
  4. 提供 25ml 的 汤A 和 75ml 的 汤B 。

当我们把汤分配给某人之后,汤就没有了。每个回合,我们将从四种概率同为 0.25 的操作中进行分配选择。如果汤的剩余量不足以完成某次操作,我们将尽可能分配。当两种类型的汤都分配完时,停止操作。

注意 不存在先分配 100 ml 汤B 的操作。

需要返回的值: 汤A 先分配完的概率 +  汤A和汤B 同时分配完的概率 / 2。返回值在正确答案 10-5 的范围内将被认为是正确的。

示例 1:

输入: n = 50
输出: 0.62500
解释: 如果我们选择前两个操作 , A 首先将变为空。
对于第三个操作,AB 会同时变为空。
对于第四个操作,B 首先将变为空。 所以 A 变为空的总概率加上 AB 同时变为空的概率的一半是 0.25 *(1 + 1 + 0.5 + 0)= 0.625

示例 2:

输入: n = 100
输出: 0.71875

提示:

  • 0 <= n <= 10^9​​​​​​​

题解

思路

思路来源:传送门

由于我们知道共有4种分配操作,而且每次分配的汤的容量是25的倍数,因此,我们可以将n/25,得到我们能够分配的次数,在除的时候,我们应该是向上取整,因为剩的一些汤我们还是要分配一次的。 除此之外,我们假设dp[i][j]为当汤A还剩i次、汤B还剩j次分配次数时,汤A先分配完的概率 + 汤A和汤B同时分配完的概率/2,那么可得 dp[i][j] = (dp[i - 4][j] + dp[i - 3][j - 1] + dp[i - 2][j - 2] + dp[i - 1][j - 3]) / 4

边界值考虑

  • dp[0][0] = 0.5,意味着汤A和汤B同时分配完,因此汤A不可能比汤B先分配完,所以贡献值为0 + 1/2 = 0.5
  • dp[i][0] = 0,意味着汤B比汤A先分配完,因此汤A不可能比汤B先分配完,汤A也不可能和汤B同时分配完,所以贡献值为0 + 0 / 2 = 0
  • dp[0][i] = 1,意味着汤A比汤B先分配完,因此汤A不可能与汤B同时分配完,所以贡献值为1 + 0 / 2 = 1
  • 由于n的范围属于[0,10^9],那么分配次数n/25属于[0,40000000],很明显数组超限。此时我们想到汤A的平均分配次数为T(A) = (4 + 3 + 2 + 1) / 4 = 2.5,汤B的平均分配次数为T(B) =(0 + 1 + 2 + 3) / 4 = 1.5,因此我们可以知道当n越大,分配给A的次数就越多,dp[n][n]就越接近于1。

代码实现

class Solution {
    public double soupServings(int n) {
        //向上取整
        int len = (int)Math.ceil(n / 25d);
        if (len >= 200) {
            return 1;
        }
        double[][] dp = new double[len + 1][len + 1];
        dp[0][0] = 0.5;
        for (int i = 1; i <= len; i++) {
            dp[0][i] = 1;
        }
        for (int i = 1; i <= len; i++) {
            for (int j = 1; j <= len; j++) {
                dp[i][j] = 0.25 * (dp[Math.max(0, i - 4)][j] + dp[Math.max(0, i - 3)][Math.max(0, j - 1)] + dp[Math.max(0, i - 2)][Math.max(0, j - 2)] + dp[Math.max(0, i - 1)][Math.max(0, j - 3)]);
            }
        }
        return dp[len][len];
    }
}