重返现世(Min-max容斥+dp)

题目:洛谷P4707

题目描述:

\(n\)中物品,每个时刻会随机生成一种物品,其中第\(i\)种物品被生成的概率为\(\frac{p_i}{m}\),求一共生成了任意\(k\)种不同物品(一种物品允许生成多次)的期望时间

\(k \leq n \leq 1000\)\(|n - k| \leq 10\)\(0 \leq p_i \leq m\)\(\sum_{i=1}^n p_i = m\)\(m \leq 10000\)

蒟蒻题解:

假设最终所有物品都出现了,记录每个物品第一次出现的时间,那么问题就是要求第一次出现的时间第\(n-k+1\)大的期望值为多少,我们可以先让\(k = n-k+1 \leq 11\)

考虑\(kth\ Min-max\)容斥:

\[E(kthmax(S)) = \sum_{T \subseteq S} (-1)^{|T|-k} \binom{|T|-1}{k-1} E(min(T)) \]

问题转换为计算\(\sum_{T \subseteq S} (-1)^{|T|-k} \binom{|T|-1}{k-1} E(min(T))\)

对于\(E(min(T))\),为\(T\)中元素第一次出现时间的期望值,所以\(E(min(T)) = \frac{1}{\sum_{i \in T} p_i}\)

\(n\)的范围可以达到\(1000\),暴力枚举\(T\),显然是不行的,任何与\(2^n\)有关的方法也都不通,考虑用\(dp\)去做

观察式子,只要枚举所有的\(|T|\)\(\sum_{i \in T} p_i\)就能求出答案

所以可以得到一个比较暴力的\(dp\)

\[f_{i,j,k} = f_{i-1,j,k} + f_{i-1,j-1,k-p_i} \]

其中\(f_{i,j,k}\)表示对于前\(i\)个数,\(|T|\)\(j\)\(\sum_{i \in T} p_i\)\(k\)的方案数,时间复杂度为\(\mathcal O(n^2m)\),只能做\(70\)

发现这个\(dp\)的转移不太好优化,尝试在状态上稍作修改,观察哪些状态是不必要的

对于\(|T|\),添加进一个值后,\(T\)变成了\(|T|+1\)\((-1)^{|T|-k}\)变成\((-1)^{|T|+1-k}\),相当于乘上\(-1\),而对于\(\binom{|T|-1}{k-1}\)变成\(\binom{|T|}{k-1} = \binom{|T|-1}{k-1} + \binom{|T|-1}{k - 2}\),又可以化成添加之前的\(|T|-1\),中间改变的是\(k\)的值,我们可以尝试把\(|T|\)替换成和\(k\)有关的东西(这里的\(k\)是指读入后的\(n-k+1\),范围最大到\(11\)

\(dp\)方程\(f_{i,j,k}\)表示对于前\(i\)个数,\(\sum_{i \in T} p_i\)\(j\)\(k\)\(\binom{|T|-1}{k-1}\)中的\(k\),此时的\(\sum_{T} (-1)^{|T|-k} \binom{|T|-1}{k-1}\)之和

考虑状态转移,如果不选第\(i\)位,那么\(f_{i,j,k} = f_{i-1,j,k}\)

如果选了第\(i\)位,那么会使\(|T|\)增加\(1\)

可以推出:

\[\begin{align*} f_{i,j,k} &= f_{i-1,j,k} + \sum_{x \in T} (-1)^{|T|-k} \binom{|T|-1}{k-1}\\ &= f_{i-1,j,k} + \sum_{T} (-1)^{|T|+1-k} \binom{|T|}{k-1}\\ &= f_{i-1,j,k} + \sum_{T} (-1)^{|T|+1-k} [\binom{|T|-1}{k-1} + \binom{|T|-1}{k-2}]\\ &= f_{i-1,j,k} + \sum_{T} (-1)^{|T|+1-k} \binom{|T|-1}{k-1} + \sum_{T} (-1)^{|T|+1-k} \binom{|T|-1}{k-2}\\ &= f_{i-1,j,k} - \sum_{T} (-1)^{|T|-k} \binom{|T|-1}{k-1} + \sum_{T} (-1)^{|T|-(k-1)} \binom{|T|-1}{(k-1)-1}\\ &= f_{i-1,j,k} - f_{i-1,j-p_i,k} + f_{i-1,j-p_i,k-1} \end{align*}\]

考虑边界问题,对于选了多个数的可以从前面推出,考虑选了恰好一个数,即\(|T|=1\)\(f_{i,p_i,k}\)的值就为\((-1)^{1-k} \binom{0}{k-1}\),同时也为\(f_{i-1,0,k-1} - f_{i-1,0,k}\),即\(f_{i-1,0,k-1} - f_{i-1,0,k} = (-1)^{1-k} \binom{0}{k-1}\)

\(k=1\)时,\(f_{i-1,0,0} - f_{i-1,0,1} = 1\)

\(k > 1\)时,\(f_{i-1,0,k-1} - f_{i-1,0,k} = 0\)

我们可以令\(f_{i,0,0}=1\),其余初始值均为\(0\),这是一种合法的方法,但不是唯一的方法,P4707洛谷题解里似乎有好几种方法,或者也可以网上去查找

这题就这么结束了?不是这样的!

如果直接设\(f_{i,j,k}\),这样会超内存,观察\(dp\)转移

\[f_{i,j,k} = f_{i-1,j,k} - f_{i-1,j-p_i,k} + f_{i-1,j-p_i,k-1} \]

右边式子第一维是小\(1\)的,第二维、第三维也都小于等于左边式子,可以从大到小枚举二三位,从而不需要开第一维的空间

时间复杂度为\(\mathcal O(nm(n-k))\),空间复杂度为\(\mathcal O(m(n-k))\)

参考程序:

#include<bits/stdc++.h>
using namespace std;
#define Re register int
typedef long long ll;

const int N = 10005, p = 998244353;
int n, k, m, sum, ans, f[N][12];

inline int read()
{
	char c = getchar();
	int ans = 0;
	while (c < 48 || c > 57) c = getchar();
	while (c >= 48 && c <= 57) ans = (ans << 3) + (ans << 1) + (c ^ 48), c = getchar();
	return ans;
}

inline int fpow(ll x, int y)
{
	ll z = 1;
	while (y)
	{
		if (y & 1) z = z * x % p;
		x = x * x % p, y >>= 1;
	}
	return z;
}

inline void inc(int &x, int y)
{
	x += y;
	if (x >= p) x -= p;
}

inline void sub(int &x, int y)
{
	x -= y;
	if (x < 0) x += p;
}

int main()
{
	n = read(), k = n - read() + 1, m = read(), f[0][0] = 1;
	for (Re i = 1; i <= n; ++i)
	{
		int u = read();
		sum += u;
		for (Re j = sum; j >= u; --j)
			for (Re t = k; t; --t) sub(f[j][t], f[j - u][t]), inc(f[j][t], f[j - u][t - 1]);
	}
	for (Re i = 1; i <= m; ++i) ans = (ans + 1ll * f[i][k] * fpow(i, p - 2)) % p;
	ans = 1ll * ans * m % p;
	printf("%d", ans);
	return 0;
}
posted @ 2021-06-04 23:58  clfzs  阅读(61)  评论(0编辑  收藏  举报