T個の皿(tokenだけど)と、P個の置き場所がある。
T個の皿をP個の置き場に一様な確率で順番に置いていく。
このとき、最も多く置かれている置き場所が複数ある(1位タイになっている)確率を求めよ。
T, P≦50
前提知識
- 確率DP (自分の解法では微妙に違うかも)
解説
独立になるよう場合分けをして計算していく。
一位タイになっている置き場の個数tie, 一位になっている山の個数maを全探索する。
tie,maが異なる場合は独立になる。
この2つを固定するが、とりあえず最初の1~tie番目の置き場所を一位にしておく。
他の置き場所が一位であっても確率は全て対照で変わらないので、C(P, tie)をかければいい。
このとき、tie+1番目~の山はmaより少ない個数の山を作る必要がある。
この後半部分はDPで計算しよう。
まず、全ての置き場所に a1, a2, a3, ... , aP個置かれている確率を考える。
これは、どんな順番で置かれても確率は(1/P)^Pであり、置く順番についての組み合わせは同じものを含む順列なので、P!/(a1!a2!a3!...aP!)である。
つまり、P!/(a1!a2!a3!...aP!) * (1/P)^Pが確率になる。
(P! * (1/P)^P) * 1/(a1!a2!a3!...aP!)
のような感じで、1/(a1!a2!a3!...aP!)の部分だけ異なるので、ここをDPで計算出来ないか考える。
dp[i][use] := 一位じゃないi番目の置き場まで終わっていて、use個既に置いたときの1/(a1!a2!...ai)
これでi+1番目に例えば4個置いたとすれば、dp[i+1][use+4] += dp[i][use] * 1/4!と遷移していけばいい。
このDPで一位として使われていないt-tie*ma個をma以上を置かないように分配していけば答えが得られる。
アルゴリズムはこれで終わりなのだが、今回は誤差対策で少し工夫をしている
1. doubleの代わりにlong doubleを使っている
2. P!と(1/P)^Pの計算を最後に一気にやらず、適宜分配しながらやっている
2番目のせいで以下のコードがとても読みにくいのだが、50!を最後にやるのは誤差が怖くてやめた。
代わりにDPの遷移などで1/4!とかをするときに、既に使われている個数を見ながら、
1/4 * 1/3 * 1/2 * 1/1
において、ここで残り20個の皿が残っていたら、50!の50*49*...*21は処理済みなので、
20/4 * 19/3 * 18/2 * 17/1
と分配して、ついでに(1/P)^Pも分配して、
20/(4P) * 19/(3P) * 18/(2P) * 17/P
というふうにしている。
この辺の計算はpre[st][cn]で前計算している。
pre[st][cn] = P(st,cn)/(cn!*P^cn)
※ こんなふうにやったけど、もっといい考え方があるかも
DP遷移に使ったpre[st][cn]もC(st,cn)*(1/P)^cnで組み合わせ×確率で遷移してて、
普通の確率DPとして捉える方法ありそう(こんな面倒な式変形ではなくて)
typedef long double ld; ld dp[51][51]; ld pre[51][51]; ld com[51][51]; struct TieForMax { double getProbability(int t, int p) { rep(st, 1, 51) rep(cn, 1, 51) { pre[st][cn] = 1; rep(i, 0, cn) pre[st][cn] *= 1.0 * (st - i) / (cn - i) / p; } rep(i, 0, 51) com[i][0] = com[i][i] = 1; rep(i, 1, 51) rep(j, 1, i) com[i][j] = com[i - 1][j - 1] + com[i - 1][j]; ld ans = 0; rep(tie, 2, p + 1) rep(ma, 1, t + 1) { int other = (p - tie) * (ma - 1); int rest = t - tie * ma; if (other < rest or rest < 0) continue; int n = p - tie; rep(i, 0, n + 1) rep(j, 0, rest + 1) dp[i][j] = 0; dp[0][0] = 1; rep(tie2, 0, tie) dp[0][0] *= pre[t - ma * tie2][ma]; rep(i, 0, n) rep(use, 0, rest + 1) { dp[i + 1][use] += dp[i][use]; int cnt2 = rest - use; rep(nxt, 1, min(cnt2, ma - 1) + 1) { dp[i + 1][use + nxt] += dp[i][use] * pre[cnt2][nxt]; } } ans += com[p][tie] * dp[n][rest]; } return ans; } };