抽牌游戏 题解
题意简述
一副
题目分析
概率期望类题目,考虑 DP,并且期望 DP 套路是从后往前递推。
显然应该可以状压 DP,但是其非常不利于后续优化。所以尝试使用线性 DP。
DP 记录值显然是当前状态到终态需要的期望抽牌次数。状态有哪些呢?牌堆、抽到过哪些牌了。如果朴素记录就是状压了,但是我们发现,
不妨使用
明确好状态,就可以转移了。我们有
边界
我们可以把它看做二维平面内的随机游走,向左下、左、行末行走。这个往行末行走就很经典。我们可以设
上述 DP 时空复杂度
不怎么优雅的证明
设
则得到的
由
数学归纳证明。边界
发现右边
所以
我们设
于是可以
代码
取模板子
namespace Mod_Int_Class { template <typename T, typename _Tp> constexpr bool in_range(_Tp val) { return std::numeric_limits<T>::min() <= val && val <= std::numeric_limits<T>::max(); } template <typename _Tp, typename = std::enable_if_t<std::is_integral<_Tp>::value>> static constexpr inline bool is_prime(_Tp val) { if (val < 2) return false; for (_Tp i = 2; i * i <= val; ++i) if (val % i == 0) return false; return true; } template <auto _mod = 19260817, typename T = int, typename S = long long> class Mod_Int { static_assert(in_range<T>(_mod), "mod must in the range of type T."); static_assert(std::is_integral<T>::value, "type T must be an integer."); static_assert(std::is_integral<S>::value, "type S must be an integer."); public: constexpr Mod_Int() noexcept = default; template <typename _Tp, typename = std::enable_if_t<std::is_integral<_Tp>::value>> constexpr Mod_Int(_Tp v) noexcept: val(0) { if (0 <= S(v) && S(v) < mod) val = v; else val = (S(v) % mod + mod) % mod; } constexpr T const& raw() const { return this -> val; } static constexpr T mod = _mod; template <typename _Tp, typename = std::enable_if_t<std::is_integral<_Tp>::value>> constexpr friend Mod_Int pow(Mod_Int a, _Tp p) { return a ^ p; } constexpr friend Mod_Int sub(Mod_Int a, Mod_Int b) { return a - b; } constexpr friend Mod_Int& tosub(Mod_Int& a, Mod_Int b) { return a -= b; } constexpr friend Mod_Int add(Mod_Int a) { return a; } template <typename... args_t> constexpr friend Mod_Int add(Mod_Int a, args_t... args) { return a + add(args...); } constexpr friend Mod_Int mul(Mod_Int a) { return a; } template <typename... args_t> constexpr friend Mod_Int mul(Mod_Int a, args_t... args) { return a * mul(args...); } template <typename... args_t> constexpr friend Mod_Int& toadd(Mod_Int& a, args_t... b) { return a = add(a, b...); } template <typename... args_t> constexpr friend Mod_Int& tomul(Mod_Int& a, args_t... b) { return a = mul(a, b...); } template <T __mod = mod, typename = std::enable_if_t<is_prime(__mod)>> static constexpr inline T inv(T a) { assert(a != 0); return _pow(a, mod - 2); } constexpr Mod_Int& operator + () const { return *this; } constexpr Mod_Int operator - () const { return _sub(0, val); } constexpr Mod_Int inv() const { return inv(val); } constexpr friend inline Mod_Int operator + (Mod_Int a, Mod_Int b) { return _add(a.val, b.val); } constexpr friend inline Mod_Int operator - (Mod_Int a, Mod_Int b) { return _sub(a.val, b.val); } constexpr friend inline Mod_Int operator * (Mod_Int a, Mod_Int b) { return _mul(a.val, b.val); } constexpr friend inline Mod_Int operator / (Mod_Int a, Mod_Int b) { return _mul(a.val, inv(b.val)); } template <typename _Tp, typename = std::enable_if_t<std::is_integral<_Tp>::value>> constexpr friend inline Mod_Int operator ^ (Mod_Int a, _Tp p) { return _pow(a.val, p); } constexpr friend inline Mod_Int& operator += (Mod_Int& a, Mod_Int b) { return a = _add(a.val, b.val); } constexpr friend inline Mod_Int& operator -= (Mod_Int& a, Mod_Int b) { return a = _sub(a.val, b.val); } constexpr friend inline Mod_Int& operator *= (Mod_Int& a, Mod_Int b) { return a = _mul(a.val, b.val); } constexpr friend inline Mod_Int& operator /= (Mod_Int& a, Mod_Int b) { return a = _mul(a.val, inv(b.val)); } template <typename _Tp, typename = std::enable_if_t<std::is_integral<_Tp>::value>> constexpr friend inline Mod_Int& operator ^= (Mod_Int& a, _Tp p) { return a = _pow(a.val, p); } constexpr friend inline bool operator == (Mod_Int a, Mod_Int b) { return a.val == b.val; } constexpr friend inline bool operator != (Mod_Int a, Mod_Int b) { return a.val != b.val; } constexpr Mod_Int& operator ++ () { this -> val + 1 == mod ? this -> val = 0 : ++this -> val; return *this; } constexpr Mod_Int& operator -- () { this -> val == 0 ? this -> val = mod - 1 : --this -> val; return *this; } constexpr Mod_Int operator ++ (int) { Mod_Int res = *this; this -> val + 1 == mod ? this -> val = 0 : ++this -> val; return res; } constexpr Mod_Int operator -- (int) { Mod_Int res = *this; this -> val == 0 ? this -> val = mod - 1 : --this -> val; return res; } friend std::istream& operator >> (std::istream& is, Mod_Int<mod, T, S>& x) { T ipt; return is >> ipt, x = ipt, is; } friend std::ostream& operator << (std::ostream& os, Mod_Int<mod, T, S> x) { return os << x.val; } protected: T val; static constexpr inline T _add(T a, T b) { return a >= mod - b ? a + b - mod : a + b; } static constexpr inline T _sub(T a, T b) { return a < b ? a - b + mod : a - b; } static constexpr inline T _mul(T a, T b) { return static_cast<S>(a) * b % mod; } template <typename _Tp, typename = std::enable_if_t<std::is_integral<_Tp>::value>> static constexpr inline T _pow(T a, _Tp p) { T res = 1; for (; p; p >>= 1, a = _mul(a, a)) if (p & 1) res = _mul(res, a); return res; } }; using mint = Mod_Int<>; using mod_t = mint; constexpr mint operator ""_m (unsigned long long x) { return mint(x); } constexpr mint operator ""_mod (unsigned long long x) { return mint(x); } } using namespace Mod_Int_Class;
部分分 & 打表
#include <cstdio> #include <iostream> #include <limits> #include <cassert> #include <vector> using namespace std; int n, m; namespace $1 { bool check() { return n <= 1000; } void solve() { vector<vector<mint>> f(n + 1, vector<mint>(n + 1)); for (int i = n - 1; i >= 0; --i) { vector<mint> k(i + 1), b(i + 1); k[0] = 1_mod * m / (n + m - i); b[0] = 1_mod * (n - i) / (n + m - i) * f[i + 1][0] + 1; for (int j = 1; j <= i; ++j) { k[j] = 1_mod * m / (n + m - i + j) + 1_mod * j / (n + m - i + j) * k[j - 1]; b[j] = 1_mod * (n - i) / (n + m - i + j) * f[i + 1][j] + 1_mod * j / (n + m - i + j) * b[j - 1] + 1; } f[i][i] = b[i] / (1 - k[i]); for (int j = 0; j < i; ++j) f[i][j] = k[j] * f[i][i] + b[j]; } printf("%d\n", f[0][0].raw()); vector<vector<double>> g(n + 1, vector<double>(n + 1)); for (int i = n - 1; i >= 0; --i) { vector<double> k(i + 1), b(i + 1); k[0] = 1. * m / (n + m - i); b[0] = 1. * (n - i) / (n + m - i) * g[i + 1][0] + 1; for (int j = 1; j <= i; ++j) { k[j] = 1. * m / (n + m - i + j) + 1. * j / (n + m - i + j) * k[j - 1]; b[j] = 1. * (n - i) / (n + m - i + j) * g[i + 1][j] + 1. * j / (n + m - i + j) * b[j - 1] + 1; } g[i][i] = b[i] / (1 - k[i]); for (int j = 0; j < i; ++j) g[i][j] = k[j] * g[i][i] + b[j]; for (int j = 0; j <= i; ++j) printf("%.10lf ", g[i][j]); puts(""); // for (int j = 1; j <= i; ++j) // printf("%.10lf ", g[i][j] - g[i][j - 1]); // puts(""); } } } signed main() { #ifndef XuYueming freopen("toad.in", "r", stdin); freopen("toad.out", "w", stdout); #endif scanf("%d%d", &n, &m); if ($1::check()) return $1::solve(), 0; $yzh::solve(); return 0; }
正解
namespace $yzh { const int N = 1000010; mint lambda[N], mu[N]; void solve() { lambda[n] = mu[n] = 0; for (int i = n - 1; i >= 0; --i) { mu[i] = ((n - i) * mu[i + 1] - 1) / (n + m - i + 1); lambda[i] = i * mu[i] / (n - i) + lambda[i + 1] + mu[i + 1] + 1_mod * (n + m) / (n - i); } printf("%d", lambda[0].raw()); } }
卡常后
#pragma GCC optimize("Ofast", "inline", "fast-math", "unroll-loops") #include <cstdio> const int N = 1000010, mod = 19260817; int n, m, lambda, mu, Inv[N << 1]; inline int add(int a, int b) { return a >= mod - b ? a + b - mod : a + b; } signed main() { freopen("toad.in", "r", stdin); freopen("toad.out", "w", stdout); scanf("%d%d", &n, &m), Inv[1] = 1; for (register int i = 2, *I = Inv + 2; i <= n + m + 1; ++i, ++I) *I = 1ll * (mod - Inv[mod % i]) * (mod / i) % mod; for (register int i = 1; i <= n; ++i) { int t = mu; mu = 1ll * add(1ll * i * mu % mod, mod - 1) * Inv[m + i + 1] % mod; lambda = add(1ll * add(n + m, 1ll * (n - i) * mu % mod) * Inv[i] % mod, add(lambda, t)); } printf("%d", lambda); return 0; }
本文作者:XuYueming,转载请注明原文链接:https://www.cnblogs.com/XuYueming/p/18545218。
若未作特殊说明,本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】