我的计数还是太差了……
这道题现在知道三种做法。
1. 直接DP
首先显然需要min-max容斥(不知道请百度),不然很难算。
显然对于大小相同的集合答案一样,问题转化为求 (f_c) 即 (c) 只鸽子最早有一只搞满 (k) 个玉米的期望。
然后我就有了一种 (f_{c,k}) 表示 (c) 只,搞满 (k) 颗,直接DP的想法,不知道能不能做。
这道题考虑期望即枚举步数乘上概率 (sum_s s imes p(s)) 比较难搞,但是因为是 (s) 次,所以不如考虑一个经典的trick,考虑小于 (s) 的,全部贡献。
那么原式 (sum_s s imes p(s) = sum_s p'(s)),其中 (p'(s)) 为大于 (s) 步的概率。
考虑 (p'(s)),大于等于 (s) 步即 (s) 步以内里面没有一个饱,枚举里面吃了几个。那么记 (g_{c,s}) 为铥了 (s) 个,有 (c) 只,没有一个饱的概率。
但是带入计算答案的式子,计算一个 (c) 复杂度是 (O(n^2k^2)) 的,甚至还要算 (n) 个 (c)。
所以先考虑优化这个式子。第一个想法肯定是卷积。
带上计算答案的式子,对于一个 (c),有
显然可以换元,枚举 ((s - i) + (i) = s),得到
显然后者可以通过一些方法算到 (O(log n)) 或者更低。即 (x = frac{n - c}{n}),然后就变成了 (left( frac{1}{1-x} ight)^C) 这一类的式子。
那么只要能快速计算 (g_{c,s}) 就能快速得到 (f)。
显然 (g) 可以通过不断加入一个点来转移,枚举多少步,就是一个组合数卷积(其实EGF可以推的啊)。
显然表示成 EGF 可以暴力卷积。
复杂度 (O(n^2 k (log n + log k)))
目前懒得写了。
2. 更好的做法
一个截然不同的做法,直接对题目做。如果把一个鸽子得到的前 (k) 个都当做有效的,那么有效的只有 (nk) 个。
所以枚举每次有效插入之前多少个饱了的,记这个序列为 (A),那么,每要得到一个有效的,期望步数就是加上 (frac{n}{rest})。
但是得到了这个玉米我们怎么知道哪个鸽子会不会饱啊
所以假设玉米扔到了一个等待序列中,等着被安排,直到我们要迅速撑饱一个鸽子的时候组合数取一下玉米,再在剩下的鸽子里钦定一个。注意最后一个玉米是已经确定好的,所以只要选 (K - 1) 个。
如果这样做会出问题,在我们搞那个等待序列的时候,就已经多算贡献了。注意到我们的期望步数是选可选集合中的一个,因此那个玉米自带了从属的鸽子,自然会多算。所以要乘上一个 (frac{1}{rest}) 抵消掉这个贡献,以实现加入等待序列。
于是考虑转移,显然状态里记录一下已经放的玉米和已经饱的鸽子个数就可以实现 (O(1)) 转移,只要同时记录方案数和期望即可。
复杂度 (O(n^2k))。
在奇怪的地方上调了好久。拿阶乘逆来当普通的逆了……
#include <bits/stdc++.h>
const int mod = 998244353;
typedef long long LL;
int mul(int a, int b) { return (LL) a * b % mod; }
void reduce(int & x) { x += x >> 31 & mod; }
const int MAXN = 50010;
int fac[MAXN], inv[MAXN];
int C(int a, int b) { return a < b ? 0 : (LL) fac[a] * inv[b] % mod * inv[a - b] % mod; }
int f[51][MAXN], g[51][MAXN];
int n, K;
int main() {
fac[0] = fac[1] = inv[0] = inv[1] = 1;
for (int i = 2; i != MAXN; ++i) {
fac[i] = mul(fac[i - 1], i);
inv[i] = mul(inv[mod % i], mod - mod / i);
}
for (int i = 2; i != MAXN; ++i)
inv[i] = mul(inv[i - 1], inv[i]);
std::cin >> n >> K; const int T = n * K;
f[0][0] = 1;
for (int j = 0; j < T; ++j)
for (int i = 0; i < n; ++i) if (f[i][j]) {
int c1 = mul(inv[n - i], fac[n - i - 1]), c2 = mul(c1, n);
reduce(f[i][j + 1] += mul(f[i][j], c1) - mod);
reduce(g[i][j + 1] += ((LL) f[i][j] * c2 % mod + g[i][j]) * c1 % mod - mod);
c1 = mul(c1, C(j - i * K, K - 1));
reduce(f[i + 1][j + 1] += mul(f[i][j], c1) - mod);
reduce(g[i + 1][j + 1] += ((LL) f[i][j] * c2 % mod + g[i][j]) * c1 % mod - mod);
}
std::cout << mul(g[n][T], fac[n]) << std::endl;
return 0;
}
3. 最通用的做法
还是min-max容斥。但是求下面的期望我们可以直接生成函数。
我们记录下放进钦定的 (c) 个鸽子,记有效的玉米为丢个这几个钦定的鸽子的玉米。有效玉米构成了一个序列。显然它们取到 (s) 个玉米的期望步数为 (frac{sn}{c})
对一个长度,实际上只需要求出有贡献的方案数,以及总方案数。总方案数就是 (frac{1}{c^s}),即这个序列所有可能。
对于有贡献的,可以采用指数型生成函数。我们钦定第一个吃饱的是 (1),然后最后方案数只要乘上一个 (c)。
因为最后一颗是确定的,所以只需要知道第一只吃了 (k - 1) 个,其他的小于等于 (k - 1) 个的方案数。因为是排列,所以使用 EGF
不阻止你们使用Poly Ln EXP,复杂度 (O(n^2 k (log n + log k))) 。但是为了优美,所以考虑使用推式子技巧。
左边那项乘上特别简单,那么只考虑右边系数的计算。看样子不太好做,所以直接上万能的求导积分等操作,可以方便的求出递推式。
然后积分一下。但是里面求的是一个卷积,那样的话还是得用FFT。
当然可以优化了。定义
注意到 (f'(x) = f(x) - frac{x^{k}}{k!}),则
对应一下系数,因为积分了,所以 (x^{n - 1}) 贡献到 (x^{n}),于是有了一个递推式,显然可以 (O(1)) 求出一项。
再结合上面讲的,就 (O(n^2 k)) 过了此题。
这个做法是从 ( exttt{@yhx-12243}) 那里学的,/cy