# ABC314 E - Roulettes 解法の正当性がやや不安です. --- ## 問題リンク - https://atcoder.jp/contests/abc314/tasks/abc314_e ## 解法 数学的な計算のみでは答えは求まりそうないので, 期待値DPでうまくやります. ### DP - $f(x):=$高橋君が最適な戦略をとったとき,スコアが$x$以上になるまでに必要なコストの期待値 とします.この時,以下が成り立ちます. $$f(x)=\min_{i=1,2,\dots,N}{E_i}$$ ただし, $$E_i=\frac{1}{P_i}{\sum_{j=1}^{P_i}}\left(C_i+f(x-S_{i,j})\right)$$ で $E_i$ は $i$ 番目のルーレットを選んだ時を選んだ時にかかるコストの期待値を表します. $f$ の初期条件として,以下が成り立ちます. - $x\leq 0$ に対して $f(x)=0$ あとはこの通りにメモ化再帰するとうまくいきそうです... ```cpp= using ld = long double; vector<ld> dp(m + 1, -1); // auto f = [&](const auto& f, int x) -> ld { if (x <= 0) return 0; if (dp[x] >= 0) return dp[x]; dp[x] = infl; rep(i, n) { ld e = 0; // 期待値 rep(j, p[i]) e += (ld)(c[i] + f(f, x - s[i][j])) / p[i]; // 遷移 chmin(dp[x], e); } return dp[x]; }; cout << fixed << setprecision(12) << f(f, m) << '\n'; ``` が実はそうでもありません. ### 問題点 これだとサンプル2には通りません.$S_{i,j}=0$ の時に壊れます. $S_{i,j}=0$ の時,上の式のままだと$f(x)$ を求めるために$f(x)$の値を必要としますが,これはおかしいですね... ### 解消 こういう時,遷移の式をうまく整理(移項)することで解消できたりします. 今回もそれが使えます...が少しややこしいです. $f(x)$ を求める際の遷移の式に注目します. $f(x)=\min E$ なので $E_i$ が最小となるような $i$ をとります.この時,もちろん $f(x)=E_i$です. $S_{i,j}=0$ であるような $j$ の個数を $z_i$ とすることで $E_i(=f(x))$ は次のように書けます. $$E_i=f(x)=C_i+\frac{1}{P_i}\left(z_if(x)+\sum_{S_{i,j}\neq0}{f(x-S_{i,j})}\right)$$ したがって移項すると $$\left(1-\frac{z_i}{P_i}\right)f(x)=C_i+\frac{1}{P_i}\sum_{S_{i,j}\neq0}f(x-S_{i,j})$$ となり, $$f(x)=\frac{1}{P_i-z_i}\left(P_iC_i+\sum_{S_{i,j}\neq0}f(x-S_{i,j})\right)\cdots\bigstar$$ が得られます. したがって, $E_i=f(x)$ となるような $i$ に対して $\bigstar$ が成り立ち,遷移が行えます. 実際に条件を満たすような $i$ を特定するのは無理そうですが,$i=1,2,\dots,N$ に対して $E_i=f(x)$ と仮定して $\bigstar$ により $f(x)$ を求めることにします.そうして求めた $f(x)$ うち,最小のものが正しい $f(x)$ です. 以上より計算量 $O(M\sum{P_i})$ で答えである $f(M)$ 求めることができます. ### 実装 ```cpp= int main() { int n, m; read(n, m); vector<int> c(n), p(n); vector<vector<int>> s(n); rep(i, n) { read(c[i], p[i]); rep(j, p[i]) { int a; read(a); s[i].emplace_back(a); } } vector<ld> dp(m + 1, -1); auto f = [&](const auto& f, int x) -> ld { if (x <= 0) return 0; if (dp[x] >= 0) return dp[x]; dp[x] = infl; rep(i, n) { int zero = 0; ld sum = (ld)p[i] * c[i]; rep(j, p[i]) { if (s[i][j]) { sum += f(f, x - s[i][j]); } else { ++zero; } } ld e = (ld)sum / (p[i] - zero); chmin(dp[x], e); } return dp[x]; }; cout << fixed << setprecision(12) << f(f, m) << '\n'; } ``` - https://atcoder.jp/contests/abc314/submissions/44553622