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

【题目】
原题地址
n n n个人,每个人有一个权 w i w_i wi,进行 n − 1 n-1 n1轮游戏,每一轮,第 k k k个人被和谐的概率为 w k ∑ i ∈ 当 前 没 被 和 谐 的 人 w i \frac {w_k} {\sum_{i\in 当前没被和谐的人}w_i} iwiwk(要求第 k k k个人没有被和谐)。求 1 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 1n,wi105,wi105

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

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

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

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

下面要求 p ( S ) p(S) p(S),我们设 s u m ( S ) = ∑ i ∈ S w i sum(S)=\sum_{i\in S}w_i sum(S)=iSwi
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} p(S)====i=0(1Ww1+sum(S))iWw1Ww1i=0(1Ww1+sum(S))iWw1×w1+sum(S)Ww1+sum(S)w1
上面的第一步的意思就是前 i i i 1 1 1 S S S都没死,第 i + 1 i+1 i+1 1 1 1死了。
第三步无穷级数求和是因为 0 &lt; 1 − w 1 + s u m ( S ) W &lt; 1 0&lt;1-\frac {w_1+sum(S)} W &lt;1 0<1Ww1+sum(S)<1,因此这是一个收敛的无穷级数。根据经验我们有 ∑ i = 0 ∞ x i = 1 1 − x \sum_{i=0}^{\infty} x^i=\frac 1 {1-x} i=0xi=1x1,可以得到上面的柿子。

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

直接算显然还是不行的,观察到 W W W很小,我们可以构造一个生成函数 f ( x ) f(x) f(x),使得 f ( x ) f(x) f(x) i i i次项系数是分母为 i i i时的贡献系数。
观察到每多一个人,贡献系数要乘上 − 1 -1 1 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}) f(x)=xw1i=2n(1xwi)
我们现在要做的就是将若干个多项式乘起来,可以用堆来维护多项式大小进行启发式合并, N T T NTT NTT来优化多项式乘法。

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

【参考代码】

#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;
}

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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值