【题目】
原题地址
有 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
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define vi vector
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;
}
【总结】
这个概率问题的转化和这个生成函数的构造都是很妙的啊!