問題集から一問無作為に選び出題するのを繰り返したときの,既出の問題数:あるいは,多面サイコロを繰り返し振ったときの,出た目の種類数
問題集から一問無作為に選んで出題するのを繰り返したときに,既出の問題数にまつわる確率と期待値を,とある経緯で知りたくなりました.これは,面,あるいは目の数がかなり多いサイコロを繰り返し振ったときに,これまでに出た目の種類を考えるのと等価になります.助けを借りつつ,この問題に取り組んだ結果を,ここに備忘録を兼ねて書き残しておきます.
きっかけ
私は,あるウェブサービスを毎日のように利用しています.そのウェブサービスは,8000 問弱*1の問題集を使用し,サービスの利用者が問題を要求するたびに,その中から無作為に問題が一つ選ばれて出題されます*2.すでに問題集の収録問題数を超える,8000 あまりの要求がなされていますが,いまだに初出,すなわち以前に出されたことのない問題に遭遇することがあります.そこで,既出の問題数にまつわる確率と期待値を考えることにしました.
問題の定式化
上記の状況と,知りたい値を整理すると,次のようになります.
- を定数として, となる確率 を求めよ.また, として,全問が出題済みである確率 を求めよ.
- を定数として,出題済みの問題数 の期待値 を求めよ.また, として,この具体的な値を求めよ.
- を定数として, (全問が出題済み)となったときの, の期待値 を求めよ.また, として,この具体的な値を求めよ.
後の議論でも使いますが,この問題は以下と等価です.
- を定数として, となる確率 を求めよ.また, として,すべての目がすでに出た確率 を求めよ.
- を定数として,出た目の種類数 の期待値 を求めよ.また, として,この具体的な値を求めよ.
- を定数として, (すべての目が出そろった)となったときの, の期待値 を求めよ.また, として,この具体的な値を求めよ.
解答
第 1 問: 問が既出である確率 と,全問が既出である確率
[mixi]ガチャポン全種類ゲットするには・・・ - 数学 | mixiコミュニティにあるように,この確率は以下で与えられます.ただし, は第 2 種 Stirling 数を意味します.
また, のとき,全問がすでに出そろっている確率は, となります.この値は,Windows 10 上の数式処理システム Maxima で,2 分程度かけて得たものです.出題した問題数(のべ)が,問題集の収録問題数に達したからといって,全問が既出になることはまずないということがわかります.
第 2 問:既出の問題数の期待値
それでは,のべ 問出題したとき,平均して何問が既出なのでしょうか.すなわち,期待値 はいくらなのでしょうか.すぐ思いつく求め方としては,上式 を直接用いて
とすることが挙げられます.しかし,今回のように が数千と大きい場合には,数式処理システムに処理させてもなかなか答えが出ないおそれがあります.そこで,別のアプローチをとることにします.これは,A Collection of Dice Problems の pp.22-23 を応用したものです.ここでは,サイコロを繰り返し振るときの,出た目の種類数の問題を扱っており,上の言いかえと状況がちょうど合致します.問題 について,のべ 回の抽出および出題の間にそれが一度でも登場するときに ,一度も登場しないときに となる確率変数 を考えます.すると,
となり,対称性から
となります.いま, 回の間に,問題 1 が一度でも登場する場合の数は,すべての場合の数から,問題 1 以外の 問だけが選ばれ続ける場合の数を除けばよいので
が成立します.よって
であるため,求める期待値は
となります.ここで とすると, となり,3000 問程度が未出題という計算になります.
第 3 問:全問が出題済みになるとき ,のべ出題回数 の期待値
これはwikipedia:クーポンコレクター問題 の一種です.求める期待値は,全 種類の異なるクーポンがあるとき,各種類のクーポンを各 1 回以上引くまでに,クーポンを引かねばならない回数 の期待値と言い換えられます.よって
となります.ここで とすると, となります.
別の方法として,第 2 問の結果を利用するというものがあります.式 を用い, を について解くと,それが求める期待値になります*3.
別解:動的計画法により数値を得る
第 1 問では, 問が既出である確率 を,第 2 問では既出の問題数の期待値 を,それぞれ数式の形で求めました.その際に, が数千に達するために,得られた式 を用いて,それぞれの に対して具体的に の値を求め,そこから を得るのは困難であると述べました.しかし,この点について,ininsanus さんより Twitter 上で,動的計画法を用いることで,それぞれの に対する具体的な計算が可能になると,C++ による実装例とともに教えていただきました.これを私が Python 3 で書き換えたものを以下に示します.
#!/usr/bin/env python3 EPS = 1e-15 def used_quizs_prob(num_quizs, cur_requests, eps=EPS): k = num_quizs m = cur_requests dp = [0 for _ in range(k + 1)] dp[0] = 1.0 for i in range(m): ndp = [0 for _ in range(k + 1)] for j in range(0, k + 1): if dp[j] < eps: continue ratio = j / k ndp[j] += ratio * dp[j] if j + 1 <= k: ndp[j + 1] += (1 - ratio) * dp[j] dp = ndp[:] exp = sum(j * dp[j] for j in range(k + 1)) return dp, exp def main(): num_quizs = int(input()) cur_requests = int(input()) _, exp = used_quizs_prob(num_quizs, cur_requests) print("{:.6f}".format(exp)) if __name__ == "__main__": main()
ここでは,dp[k 問のうち,出題済みの問題数] := その確率
となっています.いま, 問が出題済みのとき,出題済みの問題が出る確率は ,新しい問題が出る確率は となります.この性質を利用して,確率の値を順次確定させることができます.
グラフ
上の動的計画法による方法を利用し, として を図示したものが次の図です.期待値 を中心として,急峻なピークがあることが分かります.
また,上式を用いて,期待値 の 依存性を図示したものが次の図です.