【概率+生成函数+NTT+启发式合并】LOJ2541 PKUWC2018 猎人杀

版权声明:这是蒟蒻的BLOG,神犇转载也要吱一声哦~ https://blog.csdn.net/Dream_Lolita/article/details/82948824

【题目】
原题地址
n n 个人,每个人有一个权 w i w_i ,进行 n 1 n-1 轮游戏,每一轮,第 k k 个人被和谐的概率为 w k i w i \frac {w_k} {\sum_{i\in 当前没被和谐的人}w_i} (要求第 k k 个人没有被和谐)。求 1 1 号是最后一个被和谐的概率。 1 n , w i 1 0 5 , w i 1 0 5 1\leq n,w_i\leq 10^5,\sum w_i \leq 10^5

【解题思路】
这是一道好题!
对于原问题我们很难直接算出答案,于是可以考虑容斥,我们要容斥的就是有一些人在 1 1 之后和谐的概率。
S S 为人的集合, p ( S ) p(S) S S 这个集合中所有人都在 1 1 之后和谐的概率,那么我们有:
a n s = ( 1 ) S p ( S ) ans=\sum (-1)^{|S|}p(S)

这个问题依旧很难解决,下面一步转化是我认为这道题目的精髓所在:

原问题是和谐一个人后就将一个人 w i w_i 的贡献去掉,现在我们和谐一个人后不去掉他,每次当我们和谐一个已经被和谐过了的人,我们当作一次“滑稽”,即我们再重新和谐一次,这样做与原问题实际上是等价的,可以进行简单证明:

W = i = 1 n w i , A = i w i W=\sum_{i=1}^n w_i,A=\sum_{i\in 已经被和谐的人}w_i
那么第 i i 个人是下一个和谐的概率 p i p_i 在原问题中应该是 w i W A \frac {w_i} {W-A}
在转化后的问题中应该是 p i = A W p i + w i W p_i=\frac A W p_i+\frac {w_i} W (和谐到已和谐的再和谐一次,或者和谐这个人)。化简以后等于上面那个柿子。

下面要求 p ( S ) p(S) ,我们设 s u m ( S ) = i S w i sum(S)=\sum_{i\in S}w_i
p ( S ) = i = 0 ( 1 w 1 + s u m ( S ) W ) i w 1 W = w 1 W i = 0 ( 1 w 1 + s u m ( S ) W ) i = w 1 W × W w 1 + s u m ( S ) = w 1 w 1 + s u m ( S ) \begin{aligned} p(S) = & \sum_{i=0}^{\infty} (1-\frac {w_1+sum(S)} W)^i \frac {w_1} W \\ = &\frac {w_1} W \sum_{i=0}^{\infty} (1-\frac {w_1+sum(S)} W)^i \\ = & \frac {w_1} W \times \frac W {w_1 +sum(S)}\\ = & \frac {w_1} {w_1+sum(S)} \end{aligned}
上面的第一步的意思就是前 i i 1 1 S S 都没死,第 i + 1 i+1 1 1 死了。
第三步无穷级数求和是因为 0 < 1 w 1 + s u m ( S ) W < 1 0<1-\frac {w_1+sum(S)} W <1 ,因此这是一个收敛的无穷级数。根据经验我们有 i = 0 x i = 1 1 x \sum_{i=0}^{\infty} x^i=\frac 1 {1-x} ,可以得到上面的柿子。

那么现在
a n s = ( 1 ) S w 1 w 1 + s u m ( S ) ans=\sum (-1)^{|S|} \frac {w_1} {w_1+sum(S)}
其中这个 w 1 w_1 是可以提到求和符号外面的。

直接算显然还是不行的,观察到 W W 很小,我们可以构造一个生成函数 f ( x ) f(x) ,使得 f ( x ) f(x) i i 次项系数是分母为 i i 时的贡献系数。
观察到每多一个人,贡献系数要乘上 1 -1 1 1 必须要贡献,除 1 1 以外所有人可以选则贡献或不贡献,这个形式就类似二项式。于是我们有:
f ( x ) = x w 1 i = 2 n ( 1 x w i ) f(x)=x^{w_1}\prod^n_{i=2} (1-x^{w_i})
我们现在要做的就是将若干个多项式乘起来,可以用堆来维护多项式大小进行启发式合并, N T T NTT 来优化多项式乘法。

时间复杂度 O ( W log 2 W ) O(W\cdot \log^2W)

【参考代码】

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define vi vector<int>
using namespace std;

typedef long long LL;
typedef pair<int,int> pii;
const int N=1e5+10,M=262245;
const int mod=998244353,g=3;
int n,sum,ans,c[N];

int read()
{
	int ret=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
	return ret;
}

void up(int &x,int y){x+=y;if(x>=mod)x-=mod;if(x<0)x+=mod;}
int upm(int x){return x>=mod?x-mod:x;}
int qpow(int x,int y)
{
	int ret=1;
	for(;y;y>>=1,x=(LL)x*x%mod) if(y&1) ret=(LL)ret*x%mod;
	return ret;
}

namespace NTT
{
	int m,sz,L,rev[M];
	vi C,f[N];
	priority_queue<pii>q;

	void ntt(vi &a,int n,int op)
	{
		for(int i=0;i<n;++i) if(i<rev[i]) swap(a[i],a[rev[i]]);
		for(int i=1;i<n;i<<=1)
		{
			int wn=qpow(g,(mod-1)/(i<<1));
			if(op==-1) wn=qpow(wn,mod-2);
			for(int j=0;j<n;j+=(i<<1))
			{
				int w=1;
				for(int k=0;k<i;++k,w=(LL)w*wn%mod)
				{
					int x=a[j+k],y=(LL)w*a[i+j+k]%mod;
					a[j+k]=upm(x+y);a[i+j+k]=upm(x-y+mod);
				}
			}
		}
		if(op==-1) for(int i=0,inv=qpow(n,mod-2);i<n;++i) a[i]=(LL)a[i]*inv%mod;
	}

	void init()
	{
		n=read();
		for(int i=1;i<=n;++i) c[i]=read(),sum+=c[i]; 

		f[1].resize(c[1]+1);f[1][0]=0;f[1][c[1]]=1;q.push(mkp(-c[1]-1,1));
		for(int i=2;i<=n;++i)
			f[i].resize(c[i]+1),f[i][0]=1,f[i][c[i]]=mod-1,q.push(mkp(-c[i]-1,i));
	}

	void merge(int idx,int szx,int idy,int szy)
	{
		for(sz=szx+szy,m=1,L=0;m<=sz;m<<=1) ++L;
		for(int i=0;i<m;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<(L-1));
		f[idx].resize(m);f[idy].resize(m);C.resize(m);
		ntt(f[idx],m,1);ntt(f[idy],m,1);
		for(int i=0;i<m;++i) C[i]=(LL)f[idx][i]*f[idy][i]%mod; 
		ntt(C,m,-1);
	}

	void solve()
	{
		while(q.size()>1)
		{
			int idx=q.top().se,szx=-q.top().fi;q.pop();
			int idy=q.top().se,szy=-q.top().fi;q.pop();
			merge(idx,szx,idy,szy); f[idx].clear();
			for(int i=0;i<szx+szy-1;++i) f[idx].pb(C[i]);
			q.push(mkp(-szx-szy+1,idx));
		}
		int id=q.top().se; 
		ans=0;
		for(int i=0;i<=sum;++i) up(ans,(LL)f[id][i]*qpow(i,mod-2)%mod);
		ans=(LL)ans*c[1]%mod; printf("%d\n",ans);
	}
};

int main()
{
#ifndef ONLINE_JUDGE
	freopen("LOJ2541.in","r",stdin);
	freopen("LOJ2541.out","w",stdout);
#endif
	NTT::init();NTT::solve();

	return 0;
}

【总结】
这个概率问题的转化和这个生成函数的构造都是很妙的啊!

猜你喜欢

转载自blog.csdn.net/Dream_Lolita/article/details/82948824