ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

「题解」信仰圣光

2021-06-27 16:00:28  阅读:215  来源: 互联网

标签:圣光 const int 题解 texttt 信仰 siz modInt reg


本文将同步发布于:

题目

题意简述

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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有