CF715E. Complete the Permutations

给出两个排列\(p,q\),其中有些位置不确定。

定义\(p,q\)的距离为\(p\)进行最少多少次交换操作之后可以得到\(q\)

问所有的合法方案下距离为\(k\)\(k=0\dots n-1\))的方案数。

\(n\le 250\)(实际上可以做\(n\le 5000\)


官方题解不知道在搞什么,时间复杂度是\(O(n^3\lg n)\)(用NTT)或\(O(n^{3.58})\)(用叫Karatsuba的快速乘法)。

下面是\(O(n^2)\)的阳间做法:

假如\(p,q\)已经确定,\(p\)\(q\)的距离:建图,连边\(p_i,q_i\),距离为\(n-轮换数\)

现在分析一下图中四种边(由于只关心头尾,所以链可以视作边):\((0,x),(x,0),(0,0),(x,y)\)。如果出现\((x,y)\)就把\(x,y\)缩点并把这条边去掉,如果存在\((x_1,0)\)\((0,x_2)\)\(x_1=x_2\),把这两条边去掉并换成\((0,0)\)。这样处理过后同一个\(x\)只会出现在一条边中。记最终剩下的三种边分别为一二三类边,个数分别为\(a,b,c\)。(稍微注意还要处理一下本来就存在的环)

考虑这些边连上之后环的组成。发现:

  1. 都是一类边,或都是二类边。(下文分别称其为一类环、二类环)
  2. 一类边和二类边都有,其中一类边到二类边中间有三类边连接。

分别先只考虑一类边和二类边。设\(F_i\)为此时恰好有\(i\)个一类环的方案数,设\(f_i\)为至少,算出\(f\)反演可得\(F\)。则有:

\[f_x=\sum_{j=x}^a \binom{a}{j}s(j,x)(a-j+c)^{\underline{a-j}} \]

其中\(s\)表示斯特林数。

意义:枚举\(j\)条一类边组成\(x\)个环,剩下的\(a-j\)条边都需要接后继,\((0,x)\)后面可以接\((0,x)\)\((0,0)\)

同理可以求出\(G_i\)表示恰好有\(i\)个二类环的方案数。

接下来考虑杂环的组成成分:可以发现,在计算一类边的拼接操作时,如果有\((0,x)\)后面接\((0,0)\),可以视作两者合并成\((0,0)\);二类边同理。所以可以视作:拼完了一类环和二类环,剩下的杂环全部由三类边组成,三类边还是\(c\)个(其实在前面,可以视作先拼一类边再拼二类边,由于拼二类边的时候三类边个数还是\(c\),所以并没有发生矛盾)。

\(H_i=s(c,i)\)。于是\((F*G*H)_i\)为恰好有\(i\)个环的方案数的生成函数。

时间\(O(n^2)\)


using namespace std;
#include <bits/stdc++.h>
#define N 2505
#define ll long long
#define mo 998244353
ll qpow(ll x,ll y=mo-2){
	ll r=1;
	for (;y;y>>=1,x=x*x%mo)
		if (y&1)
			r=r*x%mo;
	return r;
}
int n;
int p[N],q[N];
ll fac[N],ifac[N];
ll s[N][N];
void initCs(){
	fac[0]=1;
	for (int i=1;i<=n;++i)
		fac[i]=fac[i-1]*i%mo;
	ifac[n]=qpow(fac[n]);
	for (int i=n-1;i>=0;--i)
		ifac[i]=ifac[i+1]*(i+1)%mo;
	s[0][0]=1;
	for (int i=1;i<=n;++i)
		for (int j=1;j<=i;++j)
			s[i][j]=(s[i-1][j-1]+(i-1)*s[i-1][j])%mo;
}
ll C(int m,int n){return fac[m]*ifac[n]%mo*ifac[m-n]%mo;}
int a,b,c,o;
int dsu[N];
int getdsu(int x){return dsu[x]==x?x:dsu[x]=getdsu(dsu[x]);}
void initabc(){
	for (int i=1;i<=n;++i)
		dsu[i]=i;
	for (int i=1;i<=n;++i){
		int u=getdsu(p[i]),v=getdsu(q[i]);
		if (u && v)
			o+=(dsu[u]==dsu[v]),dsu[u]=v;
	}
	static int bz[N];
	for (int i=1;i<=n;++i){
		int u=getdsu(p[i]),v=getdsu(q[i]);
		if (!u && !v)
			c++;
		else if (!u && v){
			if (bz[v]==1)
				c++,b--;
			else				
				bz[v]=-1,a++;
		}
		else if (u && !v){
			if (bz[u]==-1)
				c++,a--;
			else
				bz[u]=1,b++;
		}
	}
}
int f[N],g[N],h[N],pro[N];
void rev(int a,int f[]){
	static int F[N];
	for (int i=0;i<=a;++i){
		ll sum=0;
		for (int j=i;j<=a;++j)
			(sum+=f[j]*C(j,i)*(j-i&1?-1:1))%=mo;
		F[i]=(sum+mo)%mo;
	}
	memcpy(f,F,sizeof(int)*(a+1));
}
void calcfg(int a,int f[]){
	for (int i=0;i<=a;++i){
		ll sum=0;
		for (int j=i;j<=a;++j)
			(sum+=C(a,j)*s[j][i]%mo*fac[a-j+c]%mo*ifac[c])%=mo;
		f[i]=sum;
	}
	rev(a,f);
}
void multi(int c[],int a[],int b[]){
	static int t[N];
	for (int i=0;i<=n;++i){
		ll sum=0;
		for (int j=0;j<=i;++j)
			sum=(sum+(ll)a[j]*b[i-j])%mo;
		t[i]=sum;
	}
	memcpy(c,t,sizeof(int)*(n+1));
}
int main(){
//	freopen("in.txt","r",stdin);
	scanf("%d",&n);
	for (int i=1;i<=n;++i) scanf("%d",&p[i]);
	for (int i=1;i<=n;++i) scanf("%d",&q[i]);
	initCs();
	initabc();
	calcfg(a,f);
	calcfg(b,g);
	for (int i=0;i<=c;++i)
		h[i]=s[c][i];
	multi(pro,f,g);
	multi(pro,pro,h);
	for (int i=0;i<n;++i)
		printf("%d ",(n-o-i>=0?pro[n-o-i]:0)*fac[c]%mo);
	return 0;
}
posted @ 2021-02-21 15:20  jz_597  阅读(68)  评论(0编辑  收藏  举报