この記事は何?
某ツイートで紹介されていた、 人の天皇の誕生日が一様独立に決まっている場合に、天皇誕生日の日数の分布と期待値を求める計算を厳密にやります *1 。
ワロタhttps://t.co/kEyQOTwjLI pic.twitter.com/5IwrSro7sx
— 鴨川を徘徊する魔女 (@Kamohai_) April 22, 2023
モチベーション
元記事だと何十時間もかかるみたいに書いてあったけど、競技プログラミングをやってる人ならすぐに計算できるよねと思ってたら書いてほしそうなコメントもらったので書いてみます。
ブログで高速な解法書きましょう
— ねぼこ (@nebocco27) April 24, 2023
問題の定式化(再確認)
人の天皇がいて、それぞれ誕生日が 日の中から一様ランダムに決まっているとき、その誕生日として現れる日 *2 *3 の数を とする。 の期待値と分布を求めよ。以下 とします *4 。
期待値
期待値だけなら簡単です。 日のうちそれぞれが天皇誕生日である確率は なので、期待値はこれらの合計の です *5 。元論文でもこの結果に言及はされているが、なぜ「なぜ一致するのかは不明である」のように記載されているかは不明である。
分布
包除原理
一般論
競技プログラミングでよく使われる包除原理を使います。包除原理は、「または」と「かつ」に関する事象の数え方で、例えば「りんごまたはみかんのいずれかを食べた人数=(りんごを食べた人数)+(みかんを食べた人数)ー(どちらも食べた人数)」のような言い換えの一般化です。 詳細は ググって ください。
本問の場合
本問では、天皇誕生日の集合が 特定の 日である確率は、 日それぞれを含む・含まないについての 通りについて包除原理を適用すると、 となります。これは階乗・ 乗計算の前計算を前提に 回の演算で計算できます。すべての について計算しても、前計算を含め全体で 回の演算で計算できるので十分高速です *6 。天皇誕生日の日数がちょうど 日である確率はこの 倍です。
コード例
以下にコード例を示します。 float をそのまま出力しています。次節以降のように桁数を増やす場合は少しいじる必要があります。
# 設定 N = 365 M = 125 # 階乗の前計算 fa = [1] for i in range(1, 400): fa.append(fa[-1] * i) def C(a, b): return fa[a] // (fa[b] * fa[a-b]) # ちょうど a 日になる確率(分母を N^M としたときの分子) def calc(a): s = 0 for i in range(a + 1): s += C(a, i) * i ** M * (-1) ** (a - i) return s * C(N, a) # 出力 for a in range(126): print(calc(a) / N ** M)
計算結果
50 桁
小数点以下 桁だとこんな感じ。
天皇誕生日がちょうど 日になる確率 | |
---|---|
厳密解
厳密に求めると書いちゃったので、正確な値も書いてみます。すべて分母が *7 の分数で書けるので、この場合の分子を記載しています。
天皇誕生日がちょうど 日になる確率(分母を としたときの分子) | |
---|---|
期待値は約 日 *8 です。
実行時間
Python で雑に実装して手元で実行すると 0.01 秒程度でした *9 。計算時間に関しては元記事もネタっぽいし、リプにもネタっぽいのがいくつかありますね。もしネタじゃなければ競技プログラミングをやれば計算量の把握や高速化が上手になるかもしれません。
これ Python のせいにされてるの何回見てもじわる https://t.co/CUa47KCD92
— きり (@kiri8128) April 25, 2023
まとめ
人の天皇の誕生日が 日の中から一様かつ独立に選ばれる場合、天皇誕生日として現れる日の日数の期待値は であることが期待値の線形性から分かる。天皇誕生日の日数がちょうど 日()である確率は上の表のとおりである。包除原理を使うと必要な演算回数は 回なので雑に実装しても一瞬で計算できる。
追記
もっと速くなるっぽいです
— 熨斗袋 (@noshi91) April 25, 2023