【题解】 P4707 重返现世

一道神仙题

首先有 kthmin-max容斥的式子:\(kth\max{S}=\sum_{T\subset S} (-1)^{|T|-k}\tbinom{|T|-1}{k-1}\min{T}\)

证明要用二项式反演,考虑算每个位置作为最大值的贡献。

然后这个东西在期望意义下也是成立的,所以我们就可以把这道题转化成对于每个集合 \(S\) 求出第一次随机到 \(S\) 中元素的期望时间。

这个也很好求,\(val(S)={\frac{1}{\sum_{i \in S}^{} p_i}}\)

但是这道题 \(n\) 高达 \(2000\),我们没法枚举每个集合算答案。

我们注意到 \(m\) 范围是 \(10000\) ,就可以考虑和值域范围有关的 dp ,就是把\(\sum_{i \in S}^{} p_i\) 设到状态里。

\(dp_{i,j,k}\) 表示前 \(i\) 个元素形成的集合大小是 \(j\)\(p_i\) 的和是 \(k\) 的方案数,转移考虑一个元素是否加入集合。

然后对每个状态算出贡献乘上方案数,70分到手。

考虑优化,我们发现维数太多不好处理,这个时候有个技巧就是把状态藏进权值里。

因为这个 \(\sum_{i \in S}^{} p_i\) 做运算会出现一大堆分数,把他放到权值里会非常的恶心,而权值的另一部分就是一个和 \(|S|\) 有关的式子 \(f(S)\),考虑把他弄进权值里。

\(dp_{i,j}\) 表示前 \(i\) 个元素, \(p_i\) 的和是 \(j\) 对应的 \((-1)^{|S|-k}\tbinom{|S|-1}{k-1}\) 的和。

还是考虑这个元素是否加入,不加入的话直接 \(dp_{i,j}+=dp_{i-1,j}\)

加入的话就有意思了,\(dp_{i,j}+=dp_{i-1,j-p_i}\times \text{???}\)

由于我们并不知道集合大小,这个 \(\text{???}\) 也不好算。

把式子稍微写一下 ,原来是 \(\sum_{S} (-1)^{|S|-k}\tbinom{|S|-1}{k-1}\) ,转移之后变成了 \(\sum_{S} (-1)^{|S|+1-k}\tbinom{|S|}{k-1}\)

把这个组合数拆开,变成 \(\sum_{S} (-1)^{|S|+1-k}(\tbinom{|S|-1}{k-2}+\tbinom{|S|-1}{k-1})\)

括号打开,变成 \(\sum_{S} (-1)^{|S|+1-k}\tbinom{|S|-1}{k-2}-\sum_{S} (-1)^{|S|-k}\tbinom{|S|-1}{k-1}\)

我们发现,后边那坨东西不就是 \(dp_{i-1}{j}\) 吗?

前边相当于把 \(k\) 减了个 \(1\),同时我们注意到 \(k\leq 10\) ,于是再记录一维 \(k\)

\(dp_{i,j,k}\) 表示前 \(i\) 个元素,\(p\) 的和是 \(j\)\((-1)^{|S|-k}\tbinom{|S|-1}{k-1}\) 的和 (注意这里的 \(k\) 不是上面的的 \(k\) 了)

根据上面的智慧东西, \(dp_{i,j,k}=dp_{i-1,j-{p_i},k-1}-dp_{i-1,j-{p_i},k}\)

注意下空间,使用滚动数组压掉第一维,就可以了。

可以了吗???

还有边界问题,这个超级容易漏的。

这里组合数的上边是负的,这时就不能按照组合意义来了,要按照下降幂那个定义来,就是稍微讨论一下就能发现除了 \(k=0\) 的时候是 \(0\) ,剩下的全是 \(-1\) ,直接按照这个设初值。

最后把 \(p_i\) 的贡献也算上就行了。

\(\sf{Code}\)

#include<bits/stdc++.h>
#define N 10010
#define MAX 2001
using namespace std;
typedef long long ll;
typedef double db;
const ll mod=998244353;
inline void read(ll &ret)
{
	ret=0;char c=getchar();bool pd=false;
	while(!isdigit(c)){pd|=c=='-';c=getchar();}
	while(isdigit(c)){ret=(ret<<1)+(ret<<3)+(c&15);c=getchar();}
	ret=pd?-ret:ret;
	return;
}
ll n,k,m,dp[2][N][15],p[N],ans;
inline ll qpow(ll a,ll b)
{
	ll ret=1;
	while(b)
	{
		if(b&1)
			ret*=a,ret%=mod;
		b>>=1;
		a*=a;
		a%=mod;
	}
	return ret;
}
signed main()
{
	read(n);
	read(k);
	read(m);
	k=n-k+1;
	for(int i=1;i<=n;i++)
		read(p[i]);
	for(int l=1;l<=k;l++)
		dp[0][0][l]=mod-1;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=m;j++)
			for(int l=0;l<=k;l++)
				dp[i&1][j][l]=0;
		for(int j=0;j<=m;j++)
		{
			for(int l=0;l<=k;l++)
			{
				dp[i&1][j][l]+=dp[(i-1)&1][j][l];
				if(j>=p[i]&&l)
					dp[i&1][j][l]+=dp[(i-1)&1][j-p[i]][l-1]-dp[(i-1)&1][j-p[i]][l];
				if(dp[i&1][j][l]>=mod)
					dp[i&1][j][l]-=mod;
				if(dp[i&1][j][l]>=mod)
					dp[i&1][j][l]-=mod;
				if(dp[i&1][j][l]<0)
					dp[i&1][j][l]+=mod;
			}
		}
	}
	for(int j=1;j<=m;j++)
		ans+=dp[n&1][j][k]*qpow(j,mod-2)%mod*m%mod;
	printf("%lld\n",ans%mod); 
	exit(0);
}
posted @ 2022-04-18 09:25  CelticOIer  阅读(47)  评论(0编辑  收藏  举报