問題
あるゲームは $N$ 面からなり、1 面から順に遊ぶ。$i$ 面は確率 $P_i\%$ で $F_i$ 秒、残りの確率で $S_i$ 秒かかる($F_i<S_i$)。
1 面から $N$ 面までを1 回の通しで、通しの所要時間の合計 $R$ 秒以内で終えれば達成である。各面を終えた直後に、やり直す(1 面の最初に戻り、通しの所要時間を 0 に戻す)ことを選べる。やり直しに時間はかからないが、それまでに遊んだ時間は総プレイ時間に含まれる。
達成までの総プレイ時間の期待値を最小にするように、いつやり直すかを選ぶ。その最小値を出力せよ。絶対誤差または相対誤差が $10^{-6}$ 以下なら正解とする。
入力形式
N R
F_1 S_1 P_1
...
F_N S_N P_N
制約
| # | 制約 |
|---|---|
| 1 | $1\le N\le50$ |
| 2 | $1\le F_i<S_i\le100$、$80\le P_i\le99$ |
| 3 | $\sum F_i\le R\le5000$ |
入出力例
入力例1
1 10
3 12 70出力例1
8.1428571429(遅いと 12 秒で制限超え。$E=0.7\cdot3+0.3(12+E)$ を解いて $E=57/7$)
入力例2
2 9
2 7 80
3 5 90出力例2
6.9500000000(1 面が遅い(7 秒)と 2 面が速くても 10 秒で間に合わない。1 面を終えた時点でやり直せば $139/20=6.95$。最後まで遊んでから気づくと 7.75)
入力例3
2 10
1 4 95
5 8 80出力例3
6.8105263158(1 面が遅い(4 秒)とき、2 面が速ければ 9 秒でまだ間に合う。それでもやり直した方が得で、答えは $647/95$。「間に合わなくなったときだけやり直す」と $75/11=6.8182$ で誤り。3 例とも、全方策を分数で厳密に評価した最小値と一致)
背景と出典
- Codeforces 865C「Gotta Go Fast」が同じ設定の問題だと私は記憶している(入出力例の値で照合したが、問題文は本セッションで未確認)。本問の入力例は自作し、制約は記憶に合わせた
- 本問の主題は解き方の比較: 不動点 $E=f(E)$ を二分探索で求める方法と、方策(どこでやり直すか)を固定すると $D(0,0)=a+bE$ が 1 次式になることを使う方策反復(Howard 1960 の policy iteration と同じ形。Newton 法とも見られる)
- これはマルコフ決定過程の「期待総費用の最小化」で、最適方策は定常・決定的なもの(状態だけで決まる)から選べる、という標準的な結果を使っている(Puterman の教科書などにあると記憶、未確認)。検算では小さい入力で全方策を列挙して確かめた
概念図
ヒント(クリックで段階的に開示)
ヒント1(方向性)
$i$ 面まで終えて通しの経過が $t$ 秒の状態 $(i,t)$ からの「残りの期待時間」を $D(i,t)$ とする。やり直すと残りは最初からと同じ $E=D(0,0)$。つまり $D$ を求めるのに答え $E$ が要る。
ヒント2(アプローチ)
$E$ を仮に $x$ と決めれば、$D$ は普通の DP で後ろから計算でき、$f(x)=D(0,0)$ が出る。
- $x>E^*$(大きすぎる)なら $f(x)<x$、$x<E^*$ なら $f(x)>x$。だから二分探索できる
- さらに、どこでやり直すかを固定すると、$D(0,0)$ は $x$ の 1 次式 $a+bx$($b$ はやり直しで最初に戻る確率)。$E=a+bE$ を解けば $E=a/(1-b)$
ヒント3(誘導)
- 各 $D$ を数 1 つでなく組 $(a,b)$ で持つ。やり直しは $(0,1)$、クリアは $(0,0)$
- 現在の推定 $E$ で $\min(E,\ \text{続ける})$ を比べて方策を決め、$(a,b)$ を後ろから計算
- 新しい $E\leftarrow a/(1-b)$。$E$ が減らなくなったら終わり。最初は「自分からはやり直さない」方策($E=\infty$)から始める
模範解答 (Python)
模範解答を表示する(まず自力で解いてから)
import sys
def main():
data = sys.stdin.buffer.read().split()
n, lim = int(data[0]), int(data[1])
F = [int(x) for x in data[2:2 + 3 * n:3]]
S = [int(x) for x in data[3:3 + 3 * n:3]]
P = [int(x) / 100 for x in data[4:4 + 3 * n:3]]
INF = float('inf')
def evaluate(E):
"""E を「やり直したときの残り期待時間」と仮定して最適方策を選び、
その方策での D(0,0) を a + b*E の形で返す(b = やり直しで始めに戻る確率)"""
# nxt_a[t], nxt_b[t]: i+1 面クリア済み・経過 t のときの a, b(t > lim は強制やり直し = 0 + 1*E)
nxt_a = [0.0] * (lim + 1)
nxt_b = [0.0] * (lim + 1)
for i in range(n - 1, -1, -1):
f, s, p = F[i], S[i], P[i]
q = 1.0 - p
cur_a = [0.0] * (lim + 1)
cur_b = [1.0] * (lim + 1) # 既定はやり直し(a=0, b=1)
tmax = lim if i > 0 else 0 # 0 面目は t=0 だけ
for t in range(tmax + 1):
tf, ts = t + f, t + s
if tf > lim:
ca = p * f + q * s # 速くても制限超え → 遊んだ後に必ずやり直し
cb = 1.0
elif ts > lim:
ca = p * (f + nxt_a[tf]) + q * s
cb = p * nxt_b[tf] + q
else:
ca = p * (f + nxt_a[tf]) + q * (s + nxt_a[ts])
cb = p * nxt_b[tf] + q * nxt_b[ts]
if i == 0 or E == INF or ca + cb * E < E: # 続けた方が得なら続ける
cur_a[t] = ca
cur_b[t] = cb
nxt_a, nxt_b = cur_a, cur_b
return nxt_a[0], nxt_b[0]
E = INF
for _ in range(100): # 方策反復(Newton 法)。実際は数回で止まる
a, b = evaluate(E)
newE = a / (1.0 - b)
if newE >= E * (1 - 1e-15):
break
E = newE
print(f"{E:.10f}")
main()
計算量: DP 1 回 $O(NR)$、それを方策反復の回数 $K$ 回。$N=50,R\le5000$ の 12 入力で $K=2\sim6$。起動込みで測った 9 入力は 0.16〜0.38 s(Python 3.14.7、M1 MacBook Pro 8GB、負荷平均 11〜18 での実測、起動込み)。
Step-by-Step 解説
1状態と式
$D(i,t)$: $i$ 面まで終えて経過 $t$。$i=N$ かつ $t\le R$ なら 0。$t>R$ なら必ずやり直すので $E$。それ以外は
$$D(i,t)=\min\Bigl(E,\ p_i\bigl(F_i+D(i+1,t+F_i)\bigr)+(1-p_i)\bigl(S_i+D(i+1,t+S_i)\bigr)\Bigr)$$
ただし $i=0$(まだ何も遊んでいない)ではやり直しは意味がないので $\min$ を取らない。答えは $E=D(0,0)$。
2不動点としての答え
$x$ を仮定して計算した $D(0,0)$ を $f(x)$ とする。$f$ は $x$ について単調増加で、傾きは 1 未満(やり直しで戻る確率 $<1$)。よって $g(x)=f(x)-x$ は減少し、根 $E^*$ がただ 1 つ。$f(x)<x$ なら $x$ は大きすぎる。これで二分探索ができる(相対誤差 $10^{-10}$ まで約 35 回)。
3方策を固定すると 1 次式
「どの状態でやり直すか」(方策)を固定すると、各 $D$ は $x$ の 1 次式 $a+bx$ になる。$\min$ は方策の選択なので、$f(x)=\min_\pi(a_\pi+b_\pi x)$ は1 次式の最小=上に凸な折れ線。
4方策反復(Newton 法)
現在の $E_k$ で最適な方策 $\pi_k$ を選び、その直線と $y=x$ の交点 $E_{k+1}=a/(1-b)$ に進む。これは凸な折れ線 $g$ に対する Newton 法で、$E_{k+1}\le E_k$ を保ちながら、方策が変わらなくなった時点で厳密に止まる(折れ線なので有限回)。実測では $K=2\sim6$ 回、小さい入力 500 個では最大 4 回・平均 2.37 回だった。
5始点
$E_0=\infty$ は「自分からはやり直さない」方策に当たる。制限 $R\ge\sum F_i$ があるので全部速ければ必ず達成でき、$b<1$ で $a/(1-b)$ が有限になる。逆に $E_0=0$ から始めると「毎回やり直す」方策になり $b=1$ で割り算できない。
迷った他案と採らなかった理由
| 案 | DP の回数 | 採否 | 理由 |
|---|---|---|---|
| 全方策の列挙(分数で厳密評価) | $2^{\text{状態数}}$ | 検算用のみ | 判断する状態が 14 個以下の入力に限る |
| $E$ の二分探索 | 約 35 回 | 不採用(遅い) | 正しいが、$N=50,R=5000$ で 1.18〜3.73 s(実測、12 入力)。同じ入力の方策反復(関数呼び出しだけで 0.09〜0.20 s)の 8〜32 倍 |
| 自分からはやり直さない($E_0=\infty$ の 1 回目で止める) | 1 回 | 不採用(誤り) | 500 入力中 183 で誤答(実測)。入力例 2 で 7.75 |
| 間に合わなくなった時点でだけやり直す($t+\sum_{j>i}F_j>R$) | 1 回 | 不採用(誤り) | 500 入力中 29 で誤答(実測)。入力例 3 で 6.8182 |
| 値反復($E$ を $f(E)$ で置き換え続ける) | 多数 | 不採用 | 収束率は $b$(やり直し確率)で、$b$ が 1 に近いと非常に遅い。$P_i=80,N=50$ なら達成率は $0.8^{50}\approx1.4\times10^{-5}$ もありうる |
| 方策反復(本解) | 2〜6 回 | 採用 | 0.16〜0.38 s |
前提が変わったら
- $R$ が $10^5$ 以上なら DP 1 回が $O(NR)$ で重くなる。各面で $t$ についての「やり直す閾値」が単調だと示せれば、閾値だけを持つ形に縮められる(本セッションでは単調性を確かめていない)
- $P_i$ が小さい(例: 50%)と達成率が $2^{-50}$ 程度になり、$1-b$ が浮動小数で桁落ちする。分数や対数で持つ必要がある
- やり直しにも時間がかかる($c$ 秒)なら、やり直しの組を $(c,1)$ にするだけで同じ方法が使える
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| メモ化再帰で $D(0,0)$ を直接呼ぶ | やり直しで自分自身を呼ぶ | $E$ を未知数として外に出す |
| 方策反復を $E_0=0$ から始める | 二分探索の下端の習慣 | 毎回やり直す方策になり $1-b=0$ で割れない。$E_0=\infty$(やり直さない)から |
| 「間に合わないときだけやり直す」 | 実行可能性だけを見る | 間に合っても、遅れが大きければやり直した方が得(入力例 3、29/500 で誤答) |
| $i=0$ でもやり直しを選ばせる | 全状態に同じ式を使う | $D(0,0)=\min(E,\cdot)$ だと $E=E$ が常に成り立ち、値が決まらない |
| 二分探索の判定を $f(x)\le x$ と $f(x)<x$ で取り違える | 単調性の向きの混同 | $f(x)<x$ なら $x$ は大きすぎる(上端を下げる) |
次のステップ
- 発展1: 各面の「やり直す閾値 $t$」が単調かどうかを全探索で調べ、単調なら閾値だけで DP を書く
- 発展2: 確率が 3 通り以上($F,M,S$)でも同じ方法が使えることを確かめる
- 発展3: Dinkelbach 法(Day131)と方策反復が、どちらも「比や不動点を 1 次式の交点で更新する Newton 法」であることを式で対応づける
検証ログ(本セッションで実行した数値)
- 全方策の列挙との照合(実測): $N=1\sim4$、$F\le4$、$S\le F+4$、$P\in[50,99]$、$R$ をランダムにした 500 入力(判断する状態 14 個以下)。各方策を
fractions.Fractionで $E=a/(1-b)$ と厳密評価した最小値と、相対誤差 $10^{-6}$ 以内で全件一致 - 誤った方法(実測、同じ 500 入力): やり直さない 183、間に合わないときだけやり直す 29 で誤答。二分探索は 0 件
- 方策反復の回数(実測): 小さい 500 入力で最大 4・平均 2.37。$N=50$ の 12 入力で 2〜6
- 二分探索との比較(実測、$N=50$): $R=2094$($=\sum F$、答え 201126.99)で方策反復 3 回 0.09 s・二分探索 37 回 1.18 s。$R=5000$ で 2 回 0.10〜0.12 s・35 回 2.99〜3.73 s
- Codeforces 865C の入力例と記憶している 3 つ(
1 8 / 2 8 81→ 3.14、2 30 / 20 30 80 / 3 9 85→ 31.4、4 面の例 → 314.1592653585)を本解で再現した - 扱っていない範囲: 閾値の単調性、$P_i$ が小さい場合の数値安定性、問題文の原典確認