백준 26219번: 별꽃의 세레나데 (Hard) [Python]

플래티넘 III 플래티넘 III

문제

별꽃의 세레나데 (Hard)

풀이

시행 횟수의 기댓값

각 종류의 피어난 꽃의 개수를 변수로 하는 DP로 해결할 수 있습니다.

각 경우의 기댓값을 구하는 경우만 알아보겠습니다.

아직 $M_i$개 만큼 피어나지 않은 꽃 중 하나가 피어날 확률을 $p$라 합시다.

이때 해당 꽃 중 하나가 피어날 때 까지 시행 횟수의 기댓값은 $1/p$입니다.

증명

확률 $p$인 경우가 일어날 때 까지의 시행 횟수의 기댓값을 구해봅시다.

한 번에 일어나는 경우 $p$, 두 번에 일어나는 경우 $(1 - p)p$, 세 번에 일어나는 경우 $(1 - p)^2p$, $\cdots$

위를 모두 합하면 $\sum_{k = 0}^\infty (k + 1)(1 - p)^kp$로 나타낼 수 있습니다.

확률 앞의 횟수 ($k + 1$)이 $1$이 되도록 전부 분할하면

$\sum_{k = 0}^\infty (1 - p)^kp + \sum_{k = 1}^\infty (1 - p)^kp + \sum_{k = 2}^\infty (1 - p)^kp + \cdots$로 나타낼 수 있습니다.

각 항을 등비수열의 합 공식을 이용해 다시 쓰면 $1 - p + (1 - p)^2 + (1 - p)^3 + \cdots$입니다.

다시 등비수열의 합 공식을 이용해 다시 쓰면 $1/p$입니다. $\blacksquare$

기댓값

아직 $M_i$만큼 피어나지 않은 꽃들의 $a_i$를 모두 합한 값을 $A$이라 합시다.

$i$번째 꽃이 하나 더 피고 나서, 모든 꽃이 $M_i$만큼 피어나게 하기 위한 기댓값을 $E_i$라 합시다.

그렇다면 현재 상태에서의 기댓값은 $1/p + \sum (E_i \times a_i / A’)$이 됩니다.

코드

파이썬에서 튜플과 딕셔너리를 사용하면 시간 초과를 받으므로 6자리 11진수를 이용해서 DP를 계산하면 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import sys

input = lambda: sys.stdin.readline().rstrip()

MOD = 998244353


def main():
    N = int(input())

    M = [0] * N
    a = [0] * N

    for i in range(N):
        M_i, a_i = map(int, input().split())
        M[i] = M_i
        a[i] = a_i
    inv = pow(sum(a), -1, MOD)

    dp = [-1] * pow(11, N)
    pow11 = [0] * (N + 1)
    pow11[0] = 1
    for i in range(1, N + 1):
        pow11[i] = pow11[i - 1] * 11

    dp = [-1] * pow11[N]
    last = 0
    for i in range(N):
        last += pow11[i] * M[i]
    dp[last] = 0

    def get_dp(num: int = 0) -> int:
        if dp[num] != -1:
            return dp[num]

        prob = 0
        a_sum = 0
        for i in range(N):
            if num % pow11[i + 1] // pow11[i] == M[i]:
                continue

            prob += a[i] * inv
            a_sum += a[i]

        ex = pow(prob, -1, MOD)
        sum_inv = pow(a_sum, -1, MOD)

        for i in range(N):
            if num % pow11[i + 1] // pow11[i] == M[i]:
                continue

            ex += get_dp(num + pow11[i]) * a[i] * sum_inv
            ex %= MOD

        dp[num] = ex
        return ex

    print(get_dp())


main()

백준 26219번: 별꽃의 세레나데 (Hard) [Python]