【题目】
原题地址
有
n
n
n个人,每个人有一个权
w
i
w_i
wi,进行
n
−
1
n-1
n−1轮游戏,每一轮,第
k
k
k个人被和谐的概率为
w
k
∑
i
∈
当
前
没
被
和
谐
的
人
w
i
\frac {w_k} {\sum_{i\in 当前没被和谐的人}w_i}
∑i∈当前没被和谐的人wiwk(要求第
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
1≤n,wi≤105,∑wi≤105
【解题思路】
这是一道好题!
对于原问题我们很难直接算出答案,于是可以考虑容斥,我们要容斥的就是有一些人在
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)∣S∣p(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=∑i∈已经被和谐的人wi。
那么第
i
i
i个人是下一个和谐的概率
p
i
p_i
pi在原问题中应该是
w
i
W
−
A
\frac {w_i} {W-A}
W−Awi。
在转化后的问题中应该是
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)=∑i∈Swi。
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∑∞(1−Ww1+sum(S))iWw1Ww1i=0∑∞(1−Ww1+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
<
1
−
w
1
+
s
u
m
(
S
)
W
<
1
0<1-\frac {w_1+sum(S)} W <1
0<1−Ww1+sum(S)<1,因此这是一个收敛的无穷级数。根据经验我们有
∑
i
=
0
∞
x
i
=
1
1
−
x
\sum_{i=0}^{\infty} x^i=\frac 1 {1-x}
∑i=0∞xi=1−x1,可以得到上面的柿子。
那么现在
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)∣S∣w1+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=2∏n(1−xwi)
我们现在要做的就是将若干个多项式乘起来,可以用堆来维护多项式大小进行启发式合并,
N
T
T
NTT
NTT来优化多项式乘法。
时间复杂度 O ( W ⋅ log 2 W ) O(W\cdot \log^2W) O(W⋅log2W)
【参考代码】
#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;
}
【总结】
这个概率问题的转化和这个生成函数的构造都是很妙的啊!