結果
問題 |
No.3055 Simple Chicken Game
|
ユーザー |
|
提出日時 | 2025-01-26 20:09:51 |
言語 | C++17 (gcc 13.3.0 + boost 1.87.0) |
結果 |
AC
|
実行時間 | 1,491 ms / 2,000 ms |
コード長 | 4,984 bytes |
コンパイル時間 | 1,489 ms |
コンパイル使用メモリ | 88,664 KB |
実行使用メモリ | 250,240 KB |
最終ジャッジ日時 | 2025-02-05 00:30:02 |
合計ジャッジ時間 | 9,874 ms |
ジャッジサーバーID (参考情報) |
judge1 / judge3 |
(要ログイン)
ファイルパターン | 結果 |
---|---|
sample | AC * 2 |
other | AC * 30 |
ソースコード
#include <algorithm> #include <cassert> #include <iostream> #include <vector> #include <atcoder/modint> // https://yukicoder.me/submissions/1039331 から以下を改善した // dp_noop, dp_flip の k を 1 次元目に移動 int main() { using mint = atcoder::modint; constexpr int kMaxN = 3000, kMinP = 100000000, kMaxP = 1000000000; int n, p; std::cin >> n >> p; assert(1 <= n && n <= kMaxN); assert(kMinP <= p && p <= kMaxP); mint::set_mod(p); const mint inv2 = mint(2).inv(), inv4 = inv2 * inv2; // dp[k][i][j] は以下を前提とする // - i: これまで操作しなかったプレイヤーの人数 // - j: まだ操作していないプレイヤーの人数 // - これまでに操作した裏が出たプレイヤーの人数が i - 1 + k 人 // dp_noop := 操作しなかったプレイヤーのうち、 // 現時点で最下位のプレイヤーにおける最終的な順位の期待値 // dp_flip := 操作して裏が出たプレイヤーのうち…… std::vector dp_noop(3, std::vector(n + 1, std::vector(n + 1, mint()))); std::vector dp_flip(3, std::vector(n + 1, std::vector(n + 1, mint()))); for (int yet = 0; yet <= n; ++yet) { for (int noop = 0; noop + yet <= n; ++noop) { for (int k = std::max(-noop, -1); k <= 1; ++k) { const int flip = noop + k; if (noop + yet + flip > n) continue; if (yet == 0) { dp_noop[k + 1][noop][yet] = noop; dp_flip[k + 1][noop][yet] = noop + flip; continue; } // 次のプレイヤーの選択を考える if (noop < flip) { // 操作しない dp_noop[k + 1][noop][yet] = dp_noop[k][noop + 1][yet - 1] - 1; dp_flip[k + 1][noop][yet] = dp_flip[k][noop + 1][yet - 1]; } else if (noop > flip) { // 操作する // 表 dp_noop[k + 1][noop][yet] += (dp_noop[k + 1][noop][yet - 1] + 1) * inv2; dp_flip[k + 1][noop][yet] += (dp_flip[k + 1][noop][yet - 1] + 1) * inv2; // 裏 dp_noop[k + 1][noop][yet] += dp_noop[k + 2][noop][yet - 1] * inv2; dp_flip[k + 1][noop][yet] += (dp_flip[k + 2][noop][yet - 1] - 1) * inv2; } else { // どちらでもよい dp_noop[k + 1][noop][yet] += (dp_noop[k][noop + 1][yet - 1] - 1) * inv2; dp_noop[k + 1][noop][yet] += (dp_noop[k + 1][noop][yet - 1] + 1) * inv4; dp_noop[k + 1][noop][yet] += dp_noop[k + 2][noop][yet - 1] * inv4; dp_flip[k + 1][noop][yet] += dp_flip[k][noop + 1][yet - 1] * inv2; dp_flip[k + 1][noop][yet] += (dp_flip[k + 1][noop][yet - 1] + 1) * inv4; dp_flip[k + 1][noop][yet] += (dp_flip[k + 2][noop][yet - 1] - 1) * inv4; } } } } // dp[i][k] := これまで最適な操作がなされてきたときに以下を満たす確率 // - 操作しなかったプレイヤーが i 人 // - 操作して裏が出たプレイヤーの人数が i - 1 + k 人 std::vector dp(1, std::vector(3, mint())); dp[0][0 + 1] = 1; for (int player = 0; player < n; ++player) { std::vector nxt(player + 2, std::vector(3, mint())); mint ans = 0; const int yet = n - 1 - player; for (int noop = 0; noop <= player; ++noop) { for (int k = std::max(-noop, -1); k <= 1; ++k) { const int flip = noop + k; if (noop + flip > player) break; const int tip = player - (noop + flip); // 表を出したプレイヤーの人数 const mint prob = dp[noop][k + 1]; // 現在のプレイヤーの選択を考える if (noop < flip) { // 操作しない ans += (tip + dp_noop[k][noop + 1][yet]) * prob; nxt[noop + 1][k] += prob; } else if (noop > flip) { // 操作する // 表 ans += (tip + 1) * prob * inv2; nxt[noop][k + 1] += prob * inv2; // 裏 ans += (tip + dp_flip[k + 2][noop][yet]) * prob * inv2; nxt[noop][k + 2] += prob * inv2; } else { // どちらでもよい assert(dp_noop[k][noop + 1][yet] == (1 + dp_flip[k + 2][noop][yet]) * inv2); ans += (tip + dp_noop[k][noop + 1][yet]) * prob; nxt[noop + 1][k] += prob * inv2; nxt[noop][k + 1] += prob * inv4; nxt[noop][k + 2] += prob * inv4; } } } dp.swap(nxt); std::cout << ans.val() << " \n"[player + 1 == n]; } return 0; }