ABC193のD問題 Pokerを解く

f:id:hrksb5029:20210301222231p:plain
ABC193で時間が足りなくて解けなかったD問題 Pokerについてのふりかえり記事です。
解説は公式がとってもわかりやすいので、自分なりに問題を解くためのポイント(気づくのに時間がかかったポイント)を挙げたいと思います。

問題をざっくりと説明すると、1〜9の数字が書かれたカードがK枚ずつあって、2名に手札が5枚ずつ配られ、そのうちの4枚がオープンになっています。この状況で高橋くんが勝つ確率を求める問題です。

atcoder.jp

大方針ですが、まずKは最大で 10^5なので、全探索はできません。
手札の種類が1〜9で、手札が1枚ずつ見えていないので、ざっくりと 9^2 = 81通り調べればいいのかなと考えつきます。
確率を求める問題なので、高橋くんが勝つケースを数えて分子にして、全ての事象の数を分母にすると求まります。

ポイント1 カードの種類の残り枚数を配列で表現する

私は最初は単純に残っているカード番号を配列に押し込めていました。
当たり前の話ですが、Kは最大で 10^5なので、配列の要素数 10^5になったら点数を計算するのも無理です。。。
カードの種類の残り枚数を記録する配列を定義します。
例えばKが3の場合、nokori = [3] * 10として、[3, 3, 3, 3, 3, 3, 3, 3, 3, 3]という配列を作ります。実際に使うのはnokori[1]からnokori[9]までです。

nokori = [K] * 10
for c in S:
    nokori[c] -= 1
for c in T:
    nokori[c] -= 1

2. 同じ手札が配られた場合と異なる手札の場合で場合分け

公式解説から抜粋します。

高橋くんにx、青木くんにyを配る方法の数は、C_iをまだ見えていない iの枚数として、
\begin{cases}{C_xC_y (x \neq y)}\\{C_x(C_x-1) (x=y)}\end{cases}

この x \neq y x = yのケースを場合分けする発想があればミスせずにすんなりとコードを書ききれた気がします。

3. カードのパターンはintの配列ではなくて、文字列のまま扱った方が楽?

私は結局は数字として扱うから、入力の直後にintの配列に変換しました。
ところどころでint()ってやらない方がスッキリするかなと思っていたんですけど、公式解説のコードを見ると文字で扱ったままでも全然いいなと思いました。
好みの問題かもしれません。


最後に参考にならないコードかもしれませんが、書いておきます。

K = int(input())
S = input()[:4]
S = [int(i) for i in S]
T = input()[:4]
T = [int(i) for i in T]

nokori = [K] * 10
for c in S:
    nokori[c] -= 1
for c in T:
    nokori[c] -= 1


def score(cards: list) -> int:
    score = 0
    for i in range(1, 10):
        s_num = cards.count(i)
        score += i * (10 ** s_num)
    return score


counter = 0

for i in range(1, 10):
    if nokori[i] == 0:
        continue
    for j in range(1, 10):
        if nokori[j] == 0 or i == j:
            continue
        new_s = S[:]
        new_t = T[:]
        new_s.append(i)
        new_t.append(j)
        if score(new_s) > score(new_t):
            counter += nokori[i] * nokori[j]
for i in range(1, 10):
    if nokori[i] < 2:
        continue
    new_s = S[:]
    new_t = T[:]
    new_s.append(i)
    new_t.append(i)
    if score(new_s) > score(new_t):
        counter += nokori[i] * (nokori[i] - 1)

all_pattern = 9 * K - 8
print(counter / all_pattern / (all_pattern - 1))