問題概要
$N$個のパンケーキがある. それぞれパンケーキには大きさとおいしさというパラメータが定められており, 大きさ$i$のパンケーキの美味しさは$d[i]$である.
今, 以下の手順にしたがってパンケーキを重ねていく.
- もう取れるパンケーキがないなら終了.
- まだ取っていないパンケーキを, どれか無作為に取る.
- それが最後に取ったパンケーキよりも大きければ終了する.
- 1 に戻る.
こうしてできたパンケーキタワーの美味しさを, 各パンケーキの美味しさの総和として定義するとき, パンケーキタワーの美味しさの期待値を求めなさい.
制約
- $1 \leq N \leq 250$
- $1 \leq d_i \leq 1000$
考察
DP なことはわかったけどどうやって定義すれば良いのか分からず.
有識者によると, 「$dp[i][j] := $既に$j$個食べていて, $i$以降食べれるときの期待値」と定義すればいいらしい.
遷移
1 個目以降に食べたやつは$\displaystyle\frac{1}{N}倍$, 2 個目以降に食べたやつは$\displaystyle\frac{1}{N - 1}$倍みたいな感じになってるので,
2 個目を食べるとき, (今食べたもののおいしさ) + (3 個目以降の期待値)を$\displaystyle\frac{1}{N - 1}$倍したものを返せば良い感じになる.
これを式にすると以下のようになる.
$$ dp[i][j] = \frac{\sum^N_{k=i}{d[k] + dp[k + 1][j + 1]}}{N - j} $$
反省
こういう期待値 DP 苦手. どのタイミングで割れば良いのかがわかんなくなりがち.
提出コード(C++🔆)
struct RandomPancakeStack {
int N;
vector<int> d;
double dp[255][255];
// すでにate個食べていて, cur以降食べれるときの残りの期待値
double rec(int cur, int ate) {
if (dp[cur][ate] != -1) return dp[cur][ate];
double ret = 0.0;
for (int nxt = cur; nxt < N; ++nxt) {
ret += double(d[N - 1 - nxt] + rec(nxt + 1, ate + 1)) / (N - ate);
}
return dp[cur][ate] = ret;
}
double expectedDeliciousness(vector<int> _d) {
d = _d, N = (int)_d.size();
REP(i, 255) REP(j, 255) dp[i][j] = -1;
return rec(0, 0);
}
};