leetcode *808. Water (2022.11.21)

【Subject】*808. Divide the soup

There are two types of soup, A and B. At the beginning there are n ml of each type of soup. There are four allocation operations:

Provide 100ml of soup A and 0ml of soup B.
Provide 75ml of soup A and 25ml of soup B.
Provide 50ml of soup A and 50ml of soup B.
Provide 25ml of soup A and 75ml of soup B.
After we dispense soup to someone, the soup is gone. Each round, we will choose among four actions with the same probability of 0.25. If there isn't enough soup left to complete a certain operation, we will allocate as much as possible. Stop when both types of soup are dispensed.

Note that there is no dispensing of 100 ml soup B first.

The value that needs to be returned: The probability that soup A will be finished first + the probability that soup A and soup B will be finished at the same time / 2. The return value is in the correct answer 1 0 − 5 10^{-5}105 will be considered correct.

Example 1:

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

Example 2:

输入: n = 100
输出: 0.71875

Hint:
0 <= n <= 109

[Problem-solving ideas 1] Dynamic programming

First, since the four allocation operations are all multiples of 25, we can divide n by 25 (if there is a remainder, make up 1), and change the four allocation operations into (4,0),(3,1),(2,2),(1,3), and the probability of each operation is 0.25.

When n is small, we can use dynamic programming to solve this problem. Let dp(i,j) represent the probability value obtained when there are i and j servings of soup A and soup B left respectively, that is, the probability that soup A will be finished first + the probability that soup A and soup B will be finished at the same time divided by 2. The state transition equation is:

dp(i,j)=1/4×( dp(i−4,y)+dp(i−3,y−1)+dp(i−2,y−2)+dp(i−1,y−3) )

Let's take a closer look at the boundary conditions:

When i>0, j=0, it is impossible to redistribute at this time, and soup A has not been allocated yet, and soup A will never be able to complete the allocation, at this time dp(i,j)=0;

When i=0,j=0 is satisfied, the probability of soup A and soup B being distributed at the same time is 1.0, and the probability of soup A being distributed first is 0, so dp(i,j)=1.0×0.5=0.5;

When i=0,j>0 is satisfied, no further allocation is possible at this time, the probability that soup A is allocated first is 1.0, and soup B will never be able to complete the allocation, so dp(i,j)=1.0;

So in summary, we can get the boundary conditions as follows:

d p ( i , j ) = { 0 , i>0, j=0 0.5 , i=0, j=0 1 , i=0, j>0 ​ dp(i,j)= \begin{cases} 0, & \text{i>0, j=0}\\ 0.5, & \text{i=0, j=0}\\ 1, & \text{i=0, j>0} \end{cases} ​ dp(i,j)=0,0.5,1,i>0, j=0i=0, j=0i=0, j>0

We can see that the time complexity of this dynamic programming is O(n2). Even after dividing n by 25, we still cannot get the answer in a short time, so we need to try some other ideas. We can find that there are (4,0), (3,1), (2,2), and (1,3) for each distribution operation. Then in one distribution, the average number of soup A will be distributed is expected to be E(A)=(4+3+2+1)×0.25=2.5, and the average number of soup B will be distributed is expected to be E(B)=(0+1+2+3)×0.25=1.5. Therefore, when n is very large, soup A will have a high probability of being allocated before B, and the probability of soup A being taken first should be very close to 1. In fact, when we perform actual calculations, we find that when n≥4475, the requested probability is already greater than 0.99999 (can be obtained by the above dynamic programming method), and its error with 1 (whether it is an absolute error or a relative error) is less than 1 0 − 5 10^{−5 }10−5
. _ In fact, we found that during the calculation:

n=4475, p≈0.999990211307
n=4476, p≈0.999990468596
So when n≥179×25n, we only need to output 1 as the answer. In other cases, we use dynamic programming to compute the answer.

class Solution {
    
    
    public double soupServings(int n) {
    
    
        n = (int) Math.ceil((double) n / 25);
        if (n >= 179) {
    
    
            return 1.0;
        }
        double[][] dp = new double[n + 1][n + 1];
        dp[0][0] = 0.5;
        for (int i = 1; i <= n; i++) {
    
    
            dp[0][i] = 1.0;
        }
        for (int i = 1; i <= n; i++) {
    
    
            for (int j = 1; j <= n; j++) {
    
    
                dp[i][j] = (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)]) / 4.0;
            }
        }
        return dp[n][n];
    }
}

[Problem-solving ideas 2] DFS

class Solution {
    
    
    private double[][] memo;

    public double soupServings(int n) {
    
    
        n = (int) Math.ceil((double) n / 25);
        if (n >= 179) {
    
    
            return 1.0;
        }
        memo = new double[n + 1][n + 1];
        return dfs(n, n);
    }

    public double dfs(int a, int b) {
    
    
        if (a <= 0 && b <= 0) {
    
    
            return 0.5;
        } else if (a <= 0) {
    
    
            return 1;
        } else if (b <= 0) {
    
    
            return 0;
        }
        if (memo[a][b] == 0) {
    
    
            memo[a][b] = 0.25 * (dfs(a - 4, b) + dfs(a - 3, b - 1) + dfs(a - 2, b - 2) + dfs(a - 1, b - 3));
        }
        return memo[a][b];
    }
}

Guess you like

Origin blog.csdn.net/XunCiy/article/details/127956729