「题解」信仰圣光
作者:互联网
本文将同步发布于:
题目
题意简述
给定一个长度为 \(n\) 的排列 \(p\),在上面等概率随机选择 \(k\) 个点,建一张 \(i\to p_i\) 的图,求有多大的概率这 \(k\) 个点可以标记到所有点。
\(1\leq k\leq n\leq 152501\)。
题解
破排列为环
显然,一个排列可以被化简为 \(m(m\geq 1)\) 个简单环,并且这些简单环的大小加起来为 \(n\),我们的目标就是求出将 \(k\) 个关键点放进 \(m\) 个环,每个环都至少有一个关键点的方案数。
生成函数大法好
我们很容易发现这个一个组合问题(关键点之间无序),因此我们决定使用普通型生成函数解决这个问题。
对于一个大小为 \(\texttt{siz}\) 的环,我们定义 \(f(x)=\sum\limits_{i=0}^{\texttt{siz}-1}\binom{\texttt{siz}}{i+1}x^i\) 为他的生成函数,表示对于大小为 \(\texttt{siz}\) 的环,放 \(i+1\) 个点的方案数为 \([x^i]f(x)\)。
不难发现,设共有 \(m\) 个环,则最终的答案为 \([x^{k-m}]\prod\limits_{i=1}^mf_i(x)\)。
冷静分析时间复杂度
我们考虑到对于一个大小为 \(\texttt{siz}\) 的环,其对应的多项式的度数为 \(\Theta(\texttt{siz})\),而多项式乘法的时间复杂度为 \(\Theta(n\log_2n)\),考虑哈夫曼编码理论和贪心思想,我们不难发现,每次选择两个度数最小的多项式合并可以使得复杂度最优。
参考程序
参考程序为了方便直接对多项式进行了 random_shuffle
。
#include<bits/stdc++.h>
using namespace std;
#define reg register
typedef long long ll;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
static char buf[1<<21],*p1=buf,*p2=buf;
inline int read(void){
reg char ch=getchar();
reg int res=0;
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) res=10*res+(ch^'0'),ch=getchar();
return res;
}
const int mod=998244353;
struct modInt{
int x;
inline modInt(reg int x=0):x(x){
//assert(0<=x&&x<mod);
return;
}
inline modInt operator+(const modInt& a)const{
reg int sum=x+a.x;
return sum>=mod?sum-mod:sum;
}
inline modInt operator-(const modInt& a)const{
reg int sum=x-a.x;
return sum<0?sum+mod:sum;
}
inline modInt operator*(const modInt& a)const{
return 1ll*x*a.x%mod;
}
inline void operator+=(const modInt& a){
x+=a.x;
if(x>=mod) x-=mod;
return;
}
inline void operator-=(const modInt& a){
x-=a.x;
if(x<0) x+=mod;
return;
}
inline void operator*=(const modInt& a){
x=1ll*x*a.x%mod;
return;
}
};
inline modInt fpow(modInt x,reg int exp){
modInt res=1;
while(exp){
if(exp&1)
res*=x;
x*=x,exp>>=1;
}
return res;
}
namespace Poly{
const modInt g=3;
const modInt invg=332748118;
vector<int> rev;
inline int getRev(reg int n){
reg int limit=1,l=0;
while(limit<n)
limit<<=1,++l;
if(rev.size()!=(unsigned)limit){
rev.resize(limit);
for(reg int i=0;i<limit;++i)
rev[i]=(rev[i>>1]>>1)|((i&1)<<(l-1));
}
return limit;
}
typedef vector<modInt> poly;
inline void NTT(reg poly& a,reg int limit,reg int flag){
for(reg int i=0;i<limit;++i)
if(i<rev[i])
swap(a[i],a[rev[i]]);
for(reg int i=1;i<limit;i<<=1){
modInt w(fpow(flag==1?g:invg,(mod-1)/(i<<1)));
for(reg int j=0;j<limit;j+=(i<<1)){
modInt e(1);
for(reg int k=0;k<i;++k,e*=w){
modInt x(a[j+k]),y(e*a[i+j+k]);
a[j+k]=x+y,a[i+j+k]=x-y;
}
}
}
if(flag==-1){
modInt inv=fpow(limit,mod-2);
for(reg int i=0;i<limit;++i)
a[i]*=inv;
}
return;
}
inline poly add(poly a,poly b){
a.resize(max(a.size(),b.size()));
for(reg int i=0,siz=b.size();i<siz;++i)
a[i]+=b[i];
return a;
}
inline poly mul(poly a,poly b){
reg int s=a.size()+b.size()-1;
reg int limit=getRev(s);
a.resize(limit),b.resize(limit);
NTT(a,limit,1),NTT(b,limit,1);
for(reg int i=0;i<limit;++i)
a[i]*=b[i];
NTT(a,limit,-1);
a.resize(s);
return a;
}
}
const int MAXN=152501+5;
int n,k;
int p[MAXN];
bool vis[MAXN];
modInt fac[MAXN],invfac[MAXN];
inline void init(reg int n){
fac[0]=1;
for(reg int i=1;i<=n;++i)
fac[i]=fac[i-1]*i;
invfac[n]=fpow(fac[n],mod-2);
for(reg int i=n-1;i>=0;--i)
invfac[i]=invfac[i+1]*(i+1);
return;
}
inline modInt binom(reg int n,reg int m){
if(m<0||n<m)
return 0;
else
return fac[n]*invfac[m]*invfac[n-m];
}
Poly::poly f[MAXN];
inline Poly::poly solve(reg int l,reg int r){
if(l==r)
return f[l];
else{
reg int mid=(l+r)>>1;
return Poly::mul(solve(l,mid),solve(mid+1,r));
}
}
int main(void){
init(152501);
reg int t=read();
while(t--){
n=read(),k=read();
for(reg int i=1;i<=n;++i)
p[i]=read();
fill(vis+1,vis+n+1,false);
reg int tot=0;
static int siz[MAXN];
for(reg int i=1;i<=n;++i){
if(!vis[i]){
reg int ptr=i;
reg int cnt=0;
while(!vis[ptr]){
vis[ptr]=true;
ptr=p[ptr];
++cnt;
}
siz[++tot]=cnt;
}
}
if(k<tot)
puts("0");
else{
random_shuffle(siz+1,siz+tot+1);
for(reg int i=1;i<=tot;++i){
f[i].resize(siz[i]);
for(reg int j=1;j<=siz[i];++j)
f[i][j-1]=binom(siz[i],j);
}
Poly::poly tmp=solve(1,tot);
modInt ans1=tmp[k-tot];
modInt ans2=binom(n,k);
modInt ans=ans1*fpow(ans2,mod-2);
printf("%d\n",ans.x);
}
}
return 0;
}
标签:圣光,const,int,题解,texttt,信仰,siz,modInt,reg 来源: https://www.cnblogs.com/Lu-Anlai/p/14940753.html