题意

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

对于 \(998244353\) 取模。

数据范围

\(n \le 50, k \le 1000\)

题解

\(\mathcal O(n^2k \log k)\)

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

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

\[ans = \sum_{c = 1}^{n} (-1)^{c + 1} {n \choose c} g_c
\]

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

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

\[g_c = \sum_{s \ge 1} P(x \ge s)
\]

我们相当于要算到 \(s - 1​\) 时刻,\(c​\) 只鸽子都没有被喂饱的概率。

我们为了算这个,辅助设 \(f_{c, s}​\) 为 \(c​\) 只鸽子,喂了 \(s​\) 只玉米还没有被喂饱的概率。

那么就有

\[g_c = \sum_{i \ge 1} \sum_{s = 1}^{i - 1} {i - 1 \choose s} f_{c, s} (\frac{n - c}{n})^{i - 1}
\]

对于这种式子,我们通常需要交换和式,为了方便令 \(\displaystyle p = \frac{n - c}n\) 那么有

\[g_c = \sum_{s = 1}^{c(k - 1)} f_{c, s} \sum_{t \ge 1} {s + t \choose s} p^{i - 1}
\]

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

\[(\frac 1{1 - x})^a = \sum_{i \ge 0} {a + i - 1 \choose a - 1} x^i
\]

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

那么其实就是

\[(\frac{1}{1 - p})^{s + 1} = (\frac{n}{c})^{1 + c}\\
\]

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

\[f_{c, s} = \sum_{i = 0}^{\min(s, k - 1)} {s \choose i} f_{c - 1, s - i} \frac{1}{n^i}
\]

直接做是 \(\mathcal O(n^2k^2)\) 的,用 \(NTT\) 优化就可以做到 \(\mathcal O(n^2k \log k)\) 啦。

\(\mathcal O(n^2k)\)

其实有个更高妙的做法。

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

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

\[(\prod_{i = 1}^{nk} P_{r_i}) (\sum_{i = 1}^{nk}E_{r_i})
\]

其中 \(\displaystyle P_x = \frac 1 {n - x}, E_x = \frac n{n - x}\) 前面表示的这个序列的概率(注意每个鸽子是不同的),后一项表示相邻两个有效玉米之间需要投递个数的期望。

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

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

推一下式子,那么有

\[\sum \prod_{i \le m} P_{r_i} P_{r_{m + 1}} (\sum_{i \le m} E_{r_i} + E_{r_m + 1})\\
= P_{r_{m + 1}}(\sum \prod_{i \le m} P_{r_i} \sum_{i \le m} E_{r_i}) + P_{r_{m + 1}} E_{r_{m + 1}} (\sum \prod_{i \le m} P_{r_i})\\
= P_{r_{m + 1}}f_{m, c} + P_{r_{m + 1}}E_{r_{m + 1}} g_{m, c}
\]

显然 \(r_{m+1} = c\) 。而新的概率之和只要简单地乘个 \(P_{r_{m+1}}\) 就行了。

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

代码

\(\mathcal O(n^2k \log k)\)

#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; }

\(\mathcal O(n^2k)\)

#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; }

UOJ#449. 【集训队作业2018】喂鸽子(期望dp)的更多相关文章

  1. UOJ 422 [集训队作业2018] 小Z的礼物 min-max容斥 期望 轮廓线dp

    LINK:小Z的礼物 太精髓了 我重学了一遍min-max容斥 重写了一遍按位或才写这道题的. 还是期望多少时间可以全部集齐. 相当于求出 \(E(max(S))\)表示最后一个出现的期望时间. 根据 ...

  2. uoj #450[集训队作业2018]复读机

    传送门 \(d=1\),那么任何时刻都可以\(k\)个复读机的一种,答案为\(k^n\) \(d>1\),可以枚举某个复读机的复读次数(必须是\(d\)的倍数),然后第\(i\)个复读时间为\( ...

  3. UOJ #449. 【集训队作业2018】喂鸽子

    UOJ #449. [集训队作业2018]喂鸽子 小Z是养鸽子的人.一天,小Z给鸽子们喂玉米吃.一共有n只鸽子,小Z每秒会等概率选择一只鸽子并给他一粒玉米.一只鸽子饱了当且仅当它吃了的玉米粒数量\(≥ ...

  4. 【UOJ#422】【集训队作业2018】小Z的礼物(min-max容斥,轮廓线dp)

    [UOJ#422][集训队作业2018]小Z的礼物(min-max容斥,轮廓线dp) 题面 UOJ 题解 毒瘤xzy,怎么能搬这种题当做WC模拟题QwQ 一开始开错题了,根本就不会做. 后来发现是每次 ...

  5. 【UOJ#450】【集训队作业2018】复读机(生成函数,单位根反演)

    [UOJ#450][集训队作业2018]复读机(生成函数,单位根反演) 题面 UOJ 题解 似乎是\(\mbox{Anson}\)爷的题. \(d=1\)的时候,随便怎么都行,答案就是\(k^n\). ...

  6. UOJ#422. 【集训队作业2018】小Z的礼物

    #422. [集训队作业2018]小Z的礼物 min-max容斥 转化为每个集合最早被染色的期望时间 如果有x个选择可以染色,那么期望时间就是((n-1)*m+(m-1)*n))/x 但是x会变,中途 ...

  7. UOJ#418. 【集训队作业2018】三角形

    #418. [集训队作业2018]三角形 和三角形没有关系 只要知道儿子放置的顺序,就可以直接模拟了 记录历史最大值 用一个pair(a,b):之后加上a个,期间最大值为增加b个 合并? A1+A2= ...

  8. UOJ#428. 【集训队作业2018】普通的计数题

    #428. [集训队作业2018]普通的计数题 模型转化好题 所以变成统计有标号合法的树的个数. 合法限制: 1.根标号比子树都大 2.如果儿子全是叶子,数量B中有 3.如果存在一个儿子不是叶子,数量 ...

  9. [UOJ422][集训队作业2018]小Z的礼物——轮廓线DP+min-max容斥

    题目链接: [集训队作业2018]小Z的礼物 题目要求的就是最后一个喜欢的物品的期望得到时间. 根据$min-max$容斥可以知道$E(max(S))=\sum\limits_{T\subseteq ...

  10. 2019.2.25 模拟赛T1【集训队作业2018】小Z的礼物

    T1: [集训队作业2018]小Z的礼物 我们发现我们要求的是覆盖所有集合里的元素的期望时间. 设\(t_{i,j}\)表示第一次覆盖第i行第j列的格子的时间,我们要求的是\(max\{ALL\}\) ...

随机推荐

  1. android 资源

    在进行APP开发的过程当中,会用到许多资源,比如:图片,字符串等.现对android资源知识进行简单记录. 具体的详细信息及用法,点击查看官方文档 分类      一般android资源分为可直接访问 ...

  2. vue-router 用户登陆

    有些路由页面需要用户登陆之后才能访问如(用户中心),如果用户没有登陆就访问这些页面的话就应该转换到登陆页面,登陆成功之后在进入该页面. 需要用到的知识点有:H5中的会话存储(sessionStorag ...

  3. Java的SSH框架整合

    写了好多篇的Android代码了,在写几篇关于Java的,博客园里肯定都是java的前辈啊,写的不好多给意见. SSH,什么是SSH呢,Struts+Spring+Hibernate,这三个就是整个的 ...

  4. APICloud Studio2新建应用报错和检出错误

    今天心血来潮,闲暇时间想做个移动应用app,听一哥们说APICloud开发app很方便,就查询了一下,看了之后简直就是热血沸腾,我感觉正是我一直要找的工具 信心满满的开始着手使用,看了一下介绍我选择了 ...

  5. js 学习之路9:运算符

    1. 算数运算符 运算符 描述 例子 结果 + 加 x=y+2 x=7 - 减 x=y-2 x=3 * 乘 x=y*2 x=10 / 除 x=y/2 x=2.5 % 求余数 (保留整数) x=y%2 ...

  6. centos7安装、配置、卸载jdk1.8

    环境: centos7 64bit jdk-8u121-linux-x64 一.安装jdk 1.安装jdk rpm -ivh jdk-8u121-linux-x64.rpm 2.查看是否安装成功 ja ...

  7. kvm热添加和热迁移

    a.热添加磁盘 1.创建磁盘 qemu-img create -f qcow2 web01-add01.qcow2 5G 2.附加磁盘设备 virsh attach-disk web01 /opt/w ...

  8. 订制rpm包到Centos7镜像中

    本文以CentOS 7.4 最小化镜像(CentOS-7-x86_64-Minimal-1708.iso)为模版 要达到的目的: 1.订制所需的rpm软件包集成到iso文件中 2.制作完成的ISO全自 ...

  9. js获取数组中最大值,最小值

    遍历方法 var arr =[12,14,34,566,34,98,77] var max = arr[0]; for(var i=0;i<arr.length;i++){ if(max< ...

  10. 移动端和PC端弹出遮罩层后,页面禁止滚动的解决方法及探究

    PC端解决方案 pc端的解决思路就是在弹出遮罩层的时候取消已经存在的滚动条,达到无法滚动的效果. 也就是说给body添加overflow:hidden属性即可,IE6.7下不会生效,需要给html增加 ...