【题目】
原题地址
有
个人,每个人有一个权
,进行
轮游戏,每一轮,第
个人被和谐的概率为
(要求第
个人没有被和谐)。求
号是最后一个被和谐的概率。
【解题思路】
这是一道好题!
对于原问题我们很难直接算出答案,于是可以考虑容斥,我们要容斥的就是有一些人在
之后和谐的概率。
设
为人的集合,
为
这个集合中所有人都在
之后和谐的概率,那么我们有:
这个问题依旧很难解决,下面一步转化是我认为这道题目的精髓所在:
原问题是和谐一个人后就将一个人 的贡献去掉,现在我们和谐一个人后不去掉他,每次当我们和谐一个已经被和谐过了的人,我们当作一次“滑稽”,即我们再重新和谐一次,这样做与原问题实际上是等价的,可以进行简单证明:
设
。
那么第
个人是下一个和谐的概率
在原问题中应该是
。
在转化后的问题中应该是
(和谐到已和谐的再和谐一次,或者和谐这个人)。化简以后等于上面那个柿子。
下面要求
,我们设
。
上面的第一步的意思就是前
轮
和
都没死,第
轮
死了。
第三步无穷级数求和是因为
,因此这是一个收敛的无穷级数。根据经验我们有
,可以得到上面的柿子。
那么现在
其中这个
是可以提到求和符号外面的。
直接算显然还是不行的,观察到
很小,我们可以构造一个生成函数
,使得
的
次项系数是分母为
时的贡献系数。
观察到每多一个人,贡献系数要乘上
,
必须要贡献,除
以外所有人可以选则贡献或不贡献,这个形式就类似二项式。于是我们有:
我们现在要做的就是将若干个多项式乘起来,可以用堆来维护多项式大小进行启发式合并,
来优化多项式乘法。
时间复杂度
【参考代码】
#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;
}
【总结】
这个概率问题的转化和这个生成函数的构造都是很妙的啊!