【集训队作业2018】喂鸽子
我的计数还是太差了……
这道题现在知道三种做法。
1. 直接DP
首先显然需要min-max容斥(不知道请百度),不然很难算。
显然对于大小相同的集合答案一样,问题转化为求 \(f_c\) 即 \(c\) 只鸽子最早有一只搞满 \(k\) 个玉米的期望。
然后我就有了一种 \(f_{c,k}\) 表示 \(c\) 只,搞满 \(k\) 颗,直接DP的想法,不知道能不能做。
这道题考虑期望即枚举步数乘上概率 \(\sum_s s \times p(s)\) 比较难搞,但是因为是 \(s\) 次,所以不如考虑一个经典的trick,考虑小于 \(s\) 的,全部贡献。
那么原式 \(\sum_s s \times 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} \right)^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)\) 过了此题。
这个做法是从 \(\texttt{@yhx-12243}\) 那里学的,/cy