其他分享
首页 > 其他分享> > 「题解」信仰圣光

「题解」信仰圣光

作者:互联网

本文将同步发布于:

题目

题意简述

给定一个长度为 \(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