UOJ#449. 【集训队作业2018】喂鸽子(期望dp)

题意

n 只鸽子,每只鸽子需要 k 粒玉米才能喂饱。问每次随意喂给 n 个鸽子中的一个,期望多久所有鸽子都被喂饱。

对于 998244353 取模。

数据范围

n50,k1000

题解

O(n2klogk)

题目问的是最晚喂饱的鸽子,我们用 minmax 反演变成对于每个集合问最早被喂饱的鸽子。

不难发现只有集合大小是有用的,我们等价于算:

ans=c=1n(1)c+1(nc)gc

我们只需要算 gc 了,即大小为 c 的集合中最早被喂饱鸽子的期望时间。

我们考虑把期望转成概率,即

gc=s1P(xs)

我们相当于要算到 s1 时刻,c 只鸽子都没有被喂饱的概率。

我们为了算这个,辅助设 fc,sc 只鸽子,喂了 s 只玉米还没有被喂饱的概率。

那么就有

gc=i1s=1i1(i1s)fc,s(ncn)i1

对于这种式子,我们通常需要交换和式,为了方便令 p=ncn 那么有

gc=s=1c(k1)fc,st1(s+ts)pi1

对于后面的式子,我们不难想到一个经典的生成函数形式,即

(11x)a=i0(a+i1a1)xi

证明,考虑隔板法或者二项式展开。

那么其实就是

(11p)s+1=(nc)1+c

下面我们考虑如何计算 fc,s ,我们枚举第 c 个鸽子喂了几颗玉米,那么就有

fc,s=i=0min(s,k1)(si)fc1,si1ni

直接做是 O(n2k2) 的,用 NTT 优化就可以做到 O(n2klogk) 啦。

O(n2k)

其实有个更高妙的做法。

称一粒玉米是有效玉米,当且仅当它被投喂给了一只没有饱的鸽子。那么有效玉米序列的长度是固定的 nk 。现在考虑枚举所有的有效玉米序列,计算对答案的贡献。下面记 ri 表示投喂第 i 粒有效玉米前已经有多少鸽子饱了。

那么对于一个玉米序列的贡献其实就是

(i=1nkPri)(i=1nkEri)

其中 Px=1nx,Ex=nnx 前面表示的这个序列的概率(注意每个鸽子是不同的),后一项表示相邻两个有效玉米之间需要投递个数的期望。

直接 dp 似乎不太方便。因为无法确定下一粒玉米投喂后是否会是一只鸽子吃饱。注意到贡献只和 ri 有关,而一只鸽子吃饱前是不会对 ri 产生影响的。所以可以认为一只鸽子吃饱前其有效玉米都是 **“白玉米” **。我们只在一只鸽子吃饱的时侯把白玉米染色。

这样就可以 dp 了,先强制鸽子吃饱的顺序是 1n ,最后乘 n! 。设 fm,c 表示投喂了 m 粒有效玉米,前 c 只鸽子已经饱了的贡献之和。gm,c 表示概率之和。

推一下式子,那么有

imPriPrm+1(imEri+Erm+1)=Prm+1(imPriimEri)+Prm+1Erm+1(imPri)=Prm+1fm,c+Prm+1Erm+1gm,c

显然 rm+1=c 。而新的概率之和只要简单地乘个 Prm+1 就行了。

接下来有两种转移,第一种是加入一粒白玉米,这种直接做。另一种是在 m+1 处有一只鸽子吃饱了,这种转移需要乘上 (mckk1) 表示给白玉米染色的方案数。最后有一只鸽子吃饱了 fnk,n·n! 就是答案。

代码

O(n2klogk)

#include <bits/stdc++.h> #define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endl using namespace std; template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; } template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; } inline int read() { int x(0), sgn(1); char ch(getchar()); for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1; for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48); return x * sgn; } void File() { #ifdef zjp_shadow freopen ("449.in", "r", stdin); freopen ("449.out", "w", stdout); #endif } const int N = 55, K = 1010; namespace Computation { const int Mod = 998244353, g = 3; inline int fpm(int x, int power) { int res = 1; for (; power; power >>= 1, x = 1ll * x * x % Mod) if (power & 1) res = 1ll * res * x % Mod; return res; } inline void add(int &x, int y) { if ((x += y) >= Mod) x -= Mod; } inline void sub(int &x, int y) { if ((x -= y) < 0) x += Mod; } inline int mul(int x, int y) { return 1ll * x * y % Mod; } #define div Div inline int div(int x, int y) { return 1ll * x * fpm(y, Mod - 2) % Mod; } int fac[N * K], ifac[N * K]; void Fac_Init(int maxn) { fac[0] = ifac[0] = 1; For (i, 1, maxn) fac[i] = mul(fac[i - 1], i); ifac[maxn] = fpm(fac[maxn], Mod - 2); Fordown (i, maxn - 1, 1) ifac[i] = mul(ifac[i + 1], i + 1); } inline int comb(int n, int m) { if (n < 0 || m < 0 || n < m) return 0; return mul(mul(fac[n], ifac[n - m]), ifac[m]); } } namespace Poly { using namespace Computation; const int Maxn = 1 << 20; int powg[Maxn], invpowg[Maxn]; void NTT_Init() { for (int i = 2; i < Maxn; i <<= 1) invpowg[i] = fpm(powg[i] = fpm(g, (Mod - 1) / i), Mod - 2); } int len, rev[Maxn]; void NTT(int *P, int opt) { Rep (i, len) if (i < rev[i]) swap(P[i], P[rev[i]]); for (int i = 2, p = 1; i <= len; p = i, i <<= 1) { int Wi = opt == 1 ? powg[i] : invpowg[i]; for (int j = 0; j < len; j += i) for (int k = 0, x = 1; k < p; ++ k) { int u = P[j + k], v = mul(x, P[j + k + p]); P[j + k] = (u + v) % Mod; P[j + k + p] = (u - v + Mod) % Mod; x = mul(x, Wi); } } if (!~opt) { int inv = fpm(len, Mod - 2); Rep (i, len) P[i] = mul(P[i], inv); } } int A[Maxn], B[Maxn], C[Maxn]; void Mult(int *a, int *b, int *c, int na, int nb) { int nc = na + nb, bit = 0; for (len = 1; len <= nc; len <<= 1) ++ bit; Rep (i, len) A[i] = B[i] = 0; For (i, 0, na) A[i] = a[i]; For (i, 0, nb) B[i] = b[i]; Rep (i, len) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bit - 1)); NTT(A, 1); NTT(B, 1); Rep (i, len) C[i] = mul(A[i], B[i]); NTT(C, -1); For (i, 0, nc) c[i] = C[i]; } } using namespace Computation; int n, k, f[N][N * K * 2], invn[N * K]; int a[N * K * 2], b[N * K * 2]; int main () { File(); n = read(); k = read(); Fac_Init(n * k); invn[0] = 1; invn[1] = fpm(n, Mod - 2); For (i, 2, k) invn[i] = mul(invn[i - 1], invn[1]); int ans = 0; Poly :: NTT_Init(); f[0][0] = 1; For (c, 1, n) { For (s, 0, (c - 1) * (k - 1)) a[s] = mul(f[c - 1][s], ifac[s]); For (i, 0, k - 1) b[i] = mul(invn[i], ifac[i]); Poly :: Mult(a, b, f[c], (c - 1) * (k - 1), k - 1); For (s, 0, c * (k - 1)) f[c][s] = mul(f[c][s], fac[s]); } For (c, 1, n) { int res = 0, base = div(n, c), coef = base; For (s, 0, c * (k - 1)) add(res, mul(f[c][s], coef)), coef = mul(coef, base); add(ans, mul(comb(n, c), mul(c & 1 ? 1 : Mod - 1, res))); } printf ("%d\n", ans); return 0; }

O(n2k)

#include <bits/stdc++.h> #define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endl using namespace std; template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; } template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; } inline int read() { int x(0), sgn(1); char ch(getchar()); for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1; for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48); return x * sgn; } void File() { #ifdef zjp_shadow freopen ("449.in", "r", stdin); freopen ("449.out", "w", stdout); #endif } const int N = 55, K = 1010; namespace Computation { const int Mod = 998244353; inline int fpm(int x, int power) { int res = 1; for (; power; power >>= 1, x = 1ll * x * x % Mod) if (power & 1) res = 1ll * res * x % Mod; return res; } inline void add(int &x, int y) { if ((x += y) >= Mod) x -= Mod; } inline void sub(int &x, int y) { if ((x -= y) < 0) x += Mod; } #define plus Plus inline int plus(int x, int y) { return (x += y) >= Mod ? x - Mod : x; } inline int mul(int x, int y) { return 1ll * x * y % Mod; } #define div Div inline int div(int x, int y) { return 1ll * x * fpm(y, Mod - 2) % Mod; } int fac[N * K], ifac[N * K]; void Fac_Init(int maxn) { fac[0] = ifac[0] = 1; For (i, 1, maxn) fac[i] = mul(fac[i - 1], i); ifac[maxn] = fpm(fac[maxn], Mod - 2); Fordown (i, maxn - 1, 1) ifac[i] = mul(ifac[i + 1], i + 1); } inline int comb(int n, int m) { if (n < 0 || m < 0 || n < m) return 0; return mul(mul(fac[n], ifac[n - m]), ifac[m]); } } using namespace Computation; int n, k, f[N * K][N], g[N * K][N]; int P[N], E[N], inv[N]; int main () { File(); n = read(); k = read(); Fac_Init(n * k); inv[1] = 1; For (i, 2, n) inv[i] = mul(inv[Mod % i], (Mod - Mod / i)); For (i, 0, n) P[i] = inv[n - i], E[i] = mul(n, inv[n - i]); f[0][0] = 0; g[0][0] = 1; Rep (i, n * k) For (j, 0, i / k) if (g[i][j]) { int coefg = mul(g[i][j], P[j]), coeff = plus(mul(P[j], f[i][j]), mul(mul(P[j], E[j]), g[i][j])), coef = comb(i - j * k, k - 1); add(f[i + 1][j], coeff); add(g[i + 1][j], coefg); add(f[i + 1][j + 1], mul(coef, coeff)); add(g[i + 1][j + 1], mul(coef, coefg)); } printf ("%d\n", mul(f[n * k][n], fac[n])); return 0; }

__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/10580673.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(888)  评论(1编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示