UOJ449. 【集训队作业2018】喂鸽子
UOJ449. 【集训队作业2018】喂鸽子
题目描述
小Z是养鸽子的人。一天,小Z给鸽子们喂玉米吃。一共有n只鸽子,小Z每秒会等概率选择一只鸽子并给他一粒玉米。一只鸽子饱了当且仅当它吃了的玉米粒数量\(\ge k\)。
小Z想要你告诉他,期望多少秒之后所有的鸽子都饱了。
假设答案的最简分数形式为\(\frac{a}{b}\),你需要求出\(w\),满足\(a \equiv b \cdot w \pmod{998244353} \; (0 \le w \lt 998244353)\)
数据范围
本题采用子任务的方式评测。
子任务一(\(4pts\)):\(n = 1\).
子任务二(\(11pts\)):\(k = 1\)。
子任务三(\(19pts\)):\(k = 2\)。
子任务四(\(46pts\)):\(k \le 50\)。
子任务五(\(20pts\)):无特殊限制。
对于所有的数据,\(n \le 50\),\(k \le 1000\)。
时间限制:\(\texttt{1s}\)
空间限制:\(\texttt{256MB}\)
解题思路
这题比较神仙,但如果能概率计数非常熟练,这题不是问题(反正我问题大了
讲一个我能理解的⑧/kk
一眼 \(min-max\) 容斥,将最后一个转化为第一个喂饱的 \(ans = \sum_{S} (-1)^{|S|+1}\frac nif[s]\)
\(\frac ni\) 是为了去除喂到集合外的情况数
\[f[i] = \sum_{j=0}^{(i-1)(k-1)} (\frac 1i)^{j+k} *i*(j+k)*{j+k-1\choose j}g[i-1][j]\\
g[i-1][j] = (\sum_{i=0}^{k-1}\frac {x^i}{i!})^{i-1}[j]
\]
#include <queue>
#include <vector>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define MP make_pair
#define ll long long
#define fi first
#define se second
using namespace std;
template <typename T>
void read(T &x) {
x = 0; bool f = 0;
char c = getchar();
for (;!isdigit(c);c=getchar()) if (c=='-') f=1;
for (;isdigit(c);c=getchar()) x=x*10+(c^48);
if (f) x=-x;
}
template<typename F>
inline void write(F x)
{
static short st[30];short tp=0;
if(x<0) putchar('-'),x=-x;
do st[++tp]=x%10,x/=10; while(x);
while(tp) putchar('0'|st[tp--]);
putchar('\n');
}
template <typename T>
inline void Mx(T &x, T y) { x < y && (x = y); }
template <typename T>
inline void Mn(T &x, T y) { x > y && (x = y); }
const int N = 65, K = 3005;
const int P = 998244353, G = 3;
ll jie[2000050], inv[2000050], g[N][K*N], n, k;
int lim, L, r[K*N];
ll C(ll n, ll m) { return jie[n] * inv[m] % P * inv[n-m] % P; }
ll fpw(ll x, ll mi) {
ll res = 1;
while (mi) {
if (mi & 1) res = res * x % P;
x = x * x % P, mi >>= 1;
}
return res;
}
void NTT(ll *a, int ty) {
for (int i = 0;i < lim; i++)
if (r[i] > i) swap(a[i], a[r[i]]);
for (int i = 1;i < lim; i <<= 1) {
ll wn = fpw(G, (P - 1) / (i << 1));
for (int j = 0;j < lim; j += (i << 1)) {
ll w = 1;
for (int k = 0;k < i; k++, w = w * wn % P) {
ll x = a[j + k], y = a[i + j + k] * w % P;
a[j + k] = (x + y) % P,
a[i + j + k] = (x - y + P) % P;
}
}
}
if (ty == -1) {
ll inv = fpw(lim, P - 2);
for (int i = 0;i < lim; i++) a[i] = a[i] * inv % P;
reverse(a + 1, a + lim);
}
}
int main() {
read(n), read(k); lim = n * k + k;
inv[0] = inv[1] = jie[0] = jie[1] = 1;
for (int i = 2;i <= lim; i++)
inv[i] = (P - P / i) * inv[P%i] % P,
jie[i] = jie[i-1] * i % P;
for (int i = 2;i <= lim; i++) inv[i] = inv[i-1] * inv[i] % P;
for (int i = 0;i < k; i++) g[1][i] = inv[i];
lim = 1; while (lim <= n * k) lim <<= 1, L++;
for (int i = 1;i < lim; i++)
r[i] = (r[i>>1] >> 1) | ((i & 1) << (L - 1));
NTT(g[1], 1), g[0][0] = 1;
for (int i = 2;i < n; i++)
for (int j = 0;j < lim; j++) g[i][j] = g[1][j] * g[i-1][j] % P;
NTT(g[1], -1);
for (int i = 2;i < n; i++) NTT(g[i], -1);
ll ans = 0;
for (int i = 1;i <= n; i++) {
ll res = 0;
for (int j = 0;j <= (i - 1) * k; j++)
res = (res + fpw(i, P - 1 - j - k) * (j + k) % P * C(j + k - 1, j) % P * g[i-1][j] % P * jie[j]) % P;
res = res * C(n, i) % P;
// cout << res * n % P << endl;
if (i & 1) ans += res;
else ans -= res; ans %= P;
}
return write((ans * n % P + P) % P), 0;
}
还有另一种方法
处理 \(g[i][j]\) 的方法相同
\[ans = \sum_{i=1}^n(-1)^{i+1} {n\choose i}\frac ni\left(\sum_{j=0}^{i * (k-1)} \frac {g[i][j]}{i^j}\right)
\]
这个式子就要简单多了
\[\sum_{i=1}^n i * P(i) = \sum_{i=0}^nP(x>i)
\]
这样可以将期望转化为概率,挺好用