結果

問題 No.3055 Simple Chicken Game
ユーザー emthrm
提出日時 2025-01-26 19:11:32
言語 C++23
(gcc 13.3.0 + boost 1.87.0)
結果
AC  
実行時間 1,516 ms / 2,000 ms
コード長 5,215 bytes
コンパイル時間 2,759 ms
コンパイル使用メモリ 114,028 KB
実行使用メモリ 250,368 KB
最終ジャッジ日時 2025-02-05 00:30:00
合計ジャッジ時間 11,505 ms
ジャッジサーバーID
(参考情報)
judge1 / judge4
このコードへのチャレンジ
(要ログイン)
ファイルパターン 結果
sample AC * 2
other AC * 30
権限があれば一括ダウンロードができます

ソースコード

diff #

#include <algorithm>
#include <cassert>
#include <iostream>
#include <vector>

#include <atcoder/modint>

// https://yukicoder.me/submissions/1039329 から以下を改善した
// - /2 と /4 を前計算した
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::raw(2).inv(), inv4 = inv2 * inv2;

  // dp_hoge[i][j][k] は以下を前提とする
  // - i: まだ操作していないプレイヤーの人数
  // - j: これまで操作しなかったプレイヤーの人数
  // - これまでに操作した裏が出たプレイヤーの人数が j - 1 + k 人

  // dp_noop := 操作しなかったプレイヤーのうち、
  //            現時点で最下位のプレイヤーにおける最終的な順位の期待値
  std::vector<std::vector<std::vector<mint>>> dp_noop(n + 1);
  for (int yet = 0; yet <= n; ++yet) {
    dp_noop[yet] = std::vector((n - yet + 1) / 2 + 1, std::vector(3, mint()));
    for (int noop = 0; noop <= (n - yet + 1) / 2; ++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[yet][noop][k + 1] = noop;
          continue;
        }
        // 次のプレイヤーの選択を考える
        if (noop < flip) {  // 操作しない
          dp_noop[yet][noop][k + 1] = dp_noop[yet - 1][noop + 1][k] - 1;
        } else if (noop > flip) {  // 操作する
          // 表
          dp_noop[yet][noop][k + 1] += (dp_noop[yet - 1][noop][k + 1] + 1) * inv2;
          // 裏
          dp_noop[yet][noop][k + 1] += dp_noop[yet - 1][noop][k + 2] * inv2;
        } else {  // どちらでもよい
          dp_noop[yet][noop][k + 1] += (dp_noop[yet - 1][noop + 1][k] - 1) * inv2;
          dp_noop[yet][noop][k + 1] += (dp_noop[yet - 1][noop][k + 1] + 1) * inv4;
          dp_noop[yet][noop][k + 1] += dp_noop[yet - 1][noop][k + 2] * inv4;
        }
      }
    }
  }

  // dp_flip := 操作して裏が出たプレイヤーのうち、
  //            現時点で最下位のプレイヤーにおける最終的な順位の期待値
  std::vector<std::vector<std::vector<mint>>> dp_flip(n + 1);
  for (int yet = 0; yet <= n; ++yet) {
    dp_flip[yet] = std::vector((n - yet + 1) / 2 + 1, std::vector(3, mint()));
    for (int noop = 0; noop <= (n - yet + 1) / 2; ++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_flip[yet][noop][k + 1] = noop + flip;
          continue;
        }
        // 次のプレイヤーの選択を考える
        if (noop < flip) {  // 操作しない
          dp_flip[yet][noop][k + 1] = dp_flip[yet - 1][noop + 1][k];
        } else if (noop > flip) {  // 操作する
          // 表
          dp_flip[yet][noop][k + 1] += (dp_flip[yet - 1][noop][k + 1] + 1) * inv2;
          // 裏
          dp_flip[yet][noop][k + 1] += (dp_flip[yet - 1][noop][k + 2] - 1) * inv2;
        } else {  // どちらでもよい
          dp_flip[yet][noop][k + 1] += dp_flip[yet - 1][noop + 1][k] * inv2;
          dp_flip[yet][noop][k + 1] += (dp_flip[yet - 1][noop][k + 1] + 1) * inv4;
          dp_flip[yet][noop][k + 1] += (dp_flip[yet - 1][noop][k + 2] - 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 + 1) / 2; ++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[yet][noop + 1][k]) * 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[yet][noop][k + 2]) * prob * inv2;
          nxt[noop][k + 2] += prob * inv2;
        } else {  // どちらでもよい
          assert(dp_noop[yet][noop + 1][k] == (1 + dp_flip[yet][noop][k + 2]) * inv2);
          ans += (tip + dp_noop[yet][noop + 1][k]) * 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;
}
0