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) \]

这样可以将期望转化为概率,挺好用

posted @ 2020-05-17 22:04  Hs-black  阅读(253)  评论(0编辑  收藏  举报