Codeforces 715E - Complete the Permutations(第一类斯特林数)

Codeforces 题面传送门 & 洛谷题面传送门

神仙题。在 AC 此题之前,此题已经在我的任务计划中躺了 5 个月的灰了。

首先考虑这个最短距离是什么东西,有点常识的人(大雾)应该知道,对于一个排列 \(p\) 而言,通过交换两个元素使其变成 \(1,2,3,4,\cdots,n\) 的最少步数等于 \(n\) 减去该排列中置换环的个数,因此对于两个排列 \(a,b\) 而言,将 \(a\) 变成 \(b\) 所需的最少步数即是在所有 \(a_i\)\(b_i\) 之间连 \(a_i\to b_i\) 的边后,\(n\) 减去置换环的个数,因此我们只用考虑有多少个排列对 \((a,b)\) 满足其置换环个数为 \(k,k\in[1,n]\cap\mathbb{Z}\) 即可。

考虑对于某个下标 \(i\),已经确定的位置可能有以下四种:

  • \((a_i,b_i)\)
  • \((a_i,0)\)
  • \((0,b_i)\)
  • \((0,0)\)

对于第一种情况我们可以直接建立一个并查集维护连通性,然后将 \(a_i,b_i\) 合并,如果已经连成环了就令置换环个数 \(+1\) 然后忽略这些边。这样就只剩 \((a_i,0),(0,b_i),(0,0)\) 这三种边了,但是对于中间两种情况,当 \(a_i=b_i\) 时会额外多一些限制,因此考虑对于形如 \((x,0),(0,x)\) 的边我们干脆将它们合并为 \((0,0)\),这样任意两条边的端点都不同了,我们考虑对这三种边分别讨论,假设这三种边的个数分别为 \(x,y,z\),注意到在连边的过程中有个性质:

Observation. 当我们将 \((0,b_i)\)\((0,0)\) 合并时还是会得到 \((0,0)\),即 \((0,0)\) 边的个数在与第二、三类边连边的过程中不会发生任何变化。

因此我们考虑以下过程:

  • 所有第二类边与某些第四类边连接,形成 \(p\) 个置换环
  • 所有第三类边与某些第四类边连接,形成 \(q\) 个置换环
  • 将剩余的 \(z\) 个第四类边连接,形成 \(r\) 个置换环

受到这个思想的启发,我们考虑将三步分别进行并将它们的方案乘起来。考虑设 \(f_p,g_q,h_r\) 分别表示第一、二、三步得到 \(p,q,r\) 的方案数。求 \(f_p\) 显然我们可以枚举有多少个二类边形成了环,设为 \(c\),那么剩余的二类边肯定会转化为第四类边,方案数 \(\begin{bmatrix}c\\p\end{bmatrix}·\dbinom{x}{c}·(z+x-c-1)^{\underline{x-c}}\),稍微解释一下这个式子,\(\begin{bmatrix}c\\p\end{bmatrix}\) 表示 \(c\) 条边形成 \(p\) 个置换环的方案数,\(\dbinom{x}{c}\) 表示从 \(x\) 条边中选出 \(c\) 条的方案数,\((z+x-c-1)^{\underline{x-c}}\) 表示处理剩余 \(x-c\) 条二类边的方案数,第一条边可以与剩余 \(z+x-c-1\) 条边匹配,而每处理完一条边后决策就少一个,因此方案数是一个下降幂的形式。\(g_q\) 也同理,\(h_r\) 就直接 \(\begin{bmatrix}z\\r\end{bmatrix}z!\) 即可,最后将三个数组求个 convolution 即可。

由于此题数据范围小得离谱,可以暴力递推第一类斯特林数+暴力卷积。

const int MAXN=250;
const int MOD=998244353;
int n,a[MAXN+5],b[MAXN+5],c[2][2],fa[MAXN+5],cnt=0,ans[MAXN+5];
int find(int x){return (!fa[x])?x:fa[x]=find(fa[x]);}
void merge(int x,int y){x=find(x);y=find(y);(x==y)?(cnt++):(fa[x]=y);}
int fac[MAXN+5],ifac[MAXN+5],s[MAXN+5][MAXN+5];
void init_fac(int n){
	for(int i=(fac[0]=ifac[0]=ifac[1]=1)+1;i<=n;i++) ifac[i]=1ll*ifac[MOD%i]*(MOD-MOD/i)%MOD;
	for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i-1]*ifac[i]%MOD;
	for(int i=(s[0][0]=1);i<=n;i++) for(int j=1;j<=i;j++)
		s[i][j]=(s[i-1][j-1]+1ll*(i-1)*s[i-1][j])%MOD;
}
void calc(int *f,int a,int b){
	if(!b){
		for(int i=0;i<=a;i++) f[i]=s[a][i];
		return;
	} for(int i=0;i<=a;i++) for(int j=i;j<=a;j++)
		f[i]=(f[i]+1ll*fac[a]*ifac[j]%MOD*ifac[a-j]%MOD*
		s[j][i]%MOD*fac[a+b-j-1]%MOD*ifac[b-1])%MOD;
}
int f1[MAXN+5],f2[MAXN+5],f3[MAXN+5],tmp[MAXN+5];
int in[MAXN+5],out[MAXN+5];
int main(){
	scanf("%d",&n);init_fac(MAXN);
	memset(in,0xff,sizeof(in));memset(out,0xff,sizeof(out));
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) scanf("%d",&b[i]);
	for(int i=1;i<=n;i++) if(a[i]&&b[i]) merge(a[i],b[i]);
	for(int i=1;i<=n;i++) in[b[i]]=a[i],out[a[i]]=b[i];
	for(int i=1;i<=n;i++) if(!~in[i]&&~out[i]){
		int cur=i;
		while(cur&&~out[cur]) cur=out[cur];
		if(!cur) ++c[0][1];
	}
	for(int i=1;i<=n;i++) if(~in[i]&&!~out[i]){
		int cur=i;
		while(cur&&~in[cur]) cur=in[cur];
		if(!cur) ++c[1][0];
	}
	for(int i=1;i<=n;i++) if(!a[i]&&!b[i]) c[0][0]++;
	for(int i=1;i<=n;i++) if(!in[i]){
		int cur=i;
		while(cur&&~out[cur]) cur=out[cur];
		if(!cur) ++c[0][0];
	} //printf("%d %d %d\n",c[0][0],c[1][0],c[0][1]);
	calc(f1,c[1][0],c[0][0]);calc(f2,c[0][1],c[0][0]);
	for(int i=0;i<=c[0][0];i++) f3[i]=1ll*fac[c[0][0]]*s[c[0][0]][i]%MOD;
//	for(int i=0;i<=c[1][0];i++) printf("%d%c",f1[i]," \n"[i==c[1][0]]);
//	for(int i=0;i<=c[0][1];i++) printf("%d%c",f2[i]," \n"[i==c[0][1]]);
//	for(int i=0;i<=c[0][0];i++) printf("%d%c",f3[i]," \n"[i==c[0][0]]);
	for(int i=0;i<=c[1][0];i++) for(int j=0;j<=c[0][1];j++)
		tmp[i+j]=(tmp[i+j]+1ll*f1[i]*f2[j])%MOD;
	for(int i=0;i<=c[1][0]+c[0][1];i++) for(int j=0;j<=c[0][0];j++)
		ans[i+j]=(ans[i+j]+1ll*tmp[i]*f3[j])%MOD;
	for(int i=0;i<n;i++) printf("%d%c",(n-i-cnt<0)?0:ans[n-i-cnt]," \n"[i==n-1]);
	return 0;
}
posted @ 2021-07-23 17:53  tzc_wk  阅读(55)  评论(0编辑  收藏  举报