結果
問題 |
No.3055 Simple Chicken Game
|
ユーザー |
![]() |
提出日時 | 2025-01-25 18:08:30 |
言語 | C++23 (gcc 13.3.0 + boost 1.87.0) |
結果 |
TLE
|
実行時間 | - |
コード長 | 6,280 bytes |
コンパイル時間 | 3,767 ms |
コンパイル使用メモリ | 123,244 KB |
実行使用メモリ | 14,848 KB |
最終ジャッジ日時 | 2025-02-05 00:28:47 |
合計ジャッジ時間 | 68,257 ms |
ジャッジサーバーID (参考情報) |
judge4 / judge5 |
(要ログイン)
ファイルパターン | 結果 |
---|---|
sample | AC * 2 |
other | AC * 10 TLE * 20 |
ソースコード
#include <cassert> #include <compare> #include <iostream> #include <limits> #include <numeric> #include <tuple> #include <vector> #include <atcoder/modint> namespace emthrm { template <typename T = long long> struct Rational { T num, den; Rational() : num(0), den(1) {} Rational(const T num, const T den = 1) : num(num), den(den) { // assert(den != 0); reduce(); } template <typename Real = long double> Real to_real() const { return static_cast<Real>(num) / den; } Rational& operator+=(const Rational& x) { const T g = std::gcd(den, x.den); num = num * (x.den / g) + x.num * (den / g); den *= x.den / g; reduce(); return *this; } Rational& operator-=(const Rational& x) { return *this += -x; } Rational& operator*=(const Rational& x) { const T g1 = std::gcd(num, x.den), g2 = std::gcd(den, x.num); num = (num / g1) * (x.num / g2); den = (den / g2) * (x.den / g1); reduce(); return *this; } Rational& operator/=(const Rational& x) { return *this *= Rational(x.den, x.num); } auto operator<=>(const Rational& x) const { return num * x.den <=> x.num * den; } bool operator==(const Rational& x) const { return num == x.num && den == x.den; } Rational& operator++() { if ((num += den) == 0) den = 1; return *this; } Rational operator++(int) { const Rational res = *this; ++*this; return res; } Rational& operator--() { if ((num -= den) == 0) den = 1; return *this; } Rational operator--(int) { const Rational res = *this; --*this; return res; } Rational operator+() const { return *this; } Rational operator-() const { return Rational(-num, den); } Rational operator+(const Rational& x) const { return Rational(*this) += x; } Rational operator-(const Rational& x) const { return Rational(*this) -= x; } Rational operator*(const Rational& x) const { return Rational(*this) *= x; } Rational operator/(const Rational& x) const { return Rational(*this) /= x; } friend std::ostream& operator<<(std::ostream& os, const Rational& x) { if (x.den == 1) return os << x.num; return os << x.num << '/' << x.den; } private: void reduce() { const T g = std::gcd(num, den); num /= g; den /= g; if (den < 0) { num = -num; den = -den; } } }; } // namespace emthrm using rational = emthrm::Rational<>; rational StayPlayer(const int, const int, const int, const int); rational BottomPlayer(const int, const int, const int, const int); // プレイヤー player の順位の期待値 std::tuple<rational, rational, rational> Calc( const int player, const int unreach, const int stay, const int n) { return { // 操作を行わなかったとき StayPlayer(player + 1, unreach, stay + 1, n), // 操作を行ったとき (1) 表 unreach + 1, // 操作を行ったとき (2) 裏 BottomPlayer(player + 1, unreach, stay, n), }; } // プレイヤー player の操作前を考える。 // stay グループにいるプレイヤーの中で最も順位の低いプレイヤーの順位の期待値 rational StayPlayer( const int player, const int unreach, const int stay, const int n) { if (player == n) return unreach + stay; const auto [rank_0, rank_10, rank_11] = Calc(player, unreach, stay, n); // プレイヤー player は操作を行わない方が最適 const rational ans_0 = rank_0 - 1; if (rank_0 < (rank_10 + rank_11) / 2) return ans_0; // プレイヤー player は操作を行った方が最適 rational ans_1 = 0; ans_1 += StayPlayer(player + 1, unreach + 1, stay, n); // 表 ans_1 += StayPlayer(player + 1, unreach, stay, n); // 裏 ans_1 /= 2; if ((rank_10 + rank_11) / 2 < rank_0) return ans_1; // どちらでもよい return (ans_0 + ans_1) / 2; } // プレイヤー player の操作前を考える。 // bottom グループにいるプレイヤーの中で最も順位の低いプレイヤーの順位の期待値 rational BottomPlayer( const int player, const int unreach, const int stay, const int n) { if (player == n) return n; const auto [rank_0, rank_10, rank_11] = Calc(player, unreach, stay, n); // プレイヤー player は操作を行わない方が最適 const rational ans_0 = BottomPlayer(player + 1, unreach, stay + 1, n); if (rank_0 < (rank_10 + rank_11) / 2) return ans_0; // プレイヤー player は操作を行った方が最適 rational ans_1 = 0; ans_1 += BottomPlayer(player + 1, unreach + 1, stay, n); // 表 ans_1 += rank_11 - 1; // 裏 ans_1 /= 2; if ((rank_10 + rank_11) / 2 < rank_0) return ans_1; // どちらでもよい return (ans_0 + ans_1) / 2; } // 有理数を用いて愚直に計算する 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); std::vector dp(1, std::vector{rational(1)}); for (int player = 0; player < n; ++player) { rational ans = 0; std::vector nxt(player + 2, std::vector(player + 2, rational(0))); // 上位が確定しているプレイヤー for (int in_front = 0; in_front <= player; ++in_front) { // 下位が確定しているプレイヤー for (int behind = 0; in_front + behind <= player; ++behind) { const rational prob = dp[in_front][behind]; if (prob == 0) continue; const auto [rank_0, rank_10, rank_11] = Calc(player, in_front, player - (in_front + behind), n); if (rank_0 < (rank_10 + rank_11) / 2) { ans += rank_0 * prob; nxt[in_front][behind] += prob; } else if ((rank_10 + rank_11) / 2 < rank_0) { ans += (rank_10 + rank_11) / 2 * prob; nxt[in_front + 1][behind] += prob / 2; nxt[in_front][behind + 1] += prob / 2; } else { ans += rank_0 * prob; // どちらでもよい nxt[in_front][behind] += prob / 2; nxt[in_front + 1][behind] += prob / 4; nxt[in_front][behind + 1] += prob / 4; } } } dp.swap(nxt); std::cout << (mint(ans.num) / ans.den).val() << " \n"[player + 1 == n]; } return 0; }