题解 P4778 【Counting swaps】

Link

P4778 Counting swaps

Solve

对于一个排列(p_1,p_2,...,p_n),如果从每个(i)(p_i)建一条边,显然可以得到一张图,这张图包括若干个环组成(包括自环)。最后的目标序列为(1,2,...,n),显然由(n)个自环组成。

这里我们有一个引理:

把一个长度为(n)的环变成(n)个自环,最少需要(n-1)次操作。

证明:

首先把长度为(2)的环变成(2)个自环,显然需要一种操作。

假设(∀k≤n-1),把长度不超过(k)的环变成(k)个自环最少需要(k-1)次操作。当(k=n)时,设该环为(v_1→v_2→v_3→...→v_n→v_1),任意交换(v_i,v_j(i<j))的出边后,我们得到(v_{i+1}→v_{i+2}→v_{i+3}→...→v_j→v_{i+1})(v_{1}→v_{2}→v_{i}→...→v_{j+1}→v_{j+2}→...→v_n→v_1)两个环。

两者的长度分别是(j-i)(n-(j-i))。把两者分别拆分成自环的最小交换次数为(j-i-1)(n-(j-i)-1)两者相加是(n-2),加上刚才(v_i)(v_j)的交换,总共需要(n-1)次交换。最后通过数学归纳法可知,原命题成立。

证毕

(F_n)表示用最少步数把一个长度为(n)的环变成(n)个自环,总共有多少中操作方法。由上面的证明过程可知,把一个长度为(n)的环变成(n)个自环的过程中,可以把该环拆成长度为(x)(y)的两个环,其中(x+y=n),设(T(x,y))表示有多少种交换方法可以把长度为(n)的环变成长度为(x)(y)的两个环,易得:

[T(x,y)=egin{cases} {n/2,x=y}\{n,x≠y} end{cases}]

另外,两者各自变为自环的方法数为(F_x)(F_y),步数为(x-1)(y-1)

根据多重集的排列数·加法原理和乘法原理:

[F_n=sum_{x+y=n}T(x,y)ast F_x ast F_y ast dfrac{(n-2)!}{(x-1)!(y-1)!} ]

如果最初的排列(p_1,P-2,...,p_n)由长度为(l_1,l_2,...,l_k)(k)个环组成,其中(l_1+l_2+...l_k=n),那么最终答案就是

[F_{l_1} ast F_{l_2} ast ... ast F_{l_k} ast dfrac{(n-k)!}{(l_1-1)! ast (l_2-1)! ast ... ast (l_k-1)!} ]

因为(10^9+9)是一个质数,所以就可以用乘法逆元算上面的除法,整个算法的时间复杂度是(O(n^2))。事实上,我们可以递推出(F_n)的前几项找规律,数感好的同学应该可以找出规律,或者把前几项输入到(OEIS)网站中,我们都可以得到通项公式(F_n=n^{n-2}),从而可以用快速幂优化到(O(nlogn))

代码不难,数学推理难。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int TT=1000000009,maxn=100005;
int N,a[maxn],T,vis[maxn],cnt;
typedef long long LL;
LL ans,Fc[maxn],F[maxn],size[maxn];
int read(){
	int ret=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-f;ch=getchar();}
	while(ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
	return ret*f; 
}
LL Pow(LL a,LL b){
	LL w=a,s=1;
	while(b){
		if(b&1)s=w*s%TT;
		w=w*w%TT;
		b>>=1;
	}
	return s;
}
int main(){
	freopen("P4778.in","r",stdin);
	freopen("P4778.out","w",stdout);
	T=read();
	Fc[0]=1;for(int i=1;i<maxn;i++)Fc[i]=Fc[i-1]*i%TT;
	F[1]=1;for(int i=2;i<maxn;i++)F[i]=Pow(i,i-2);
	while(T--){
		N=read();
		for(int i=1;i<=N;i++)a[i]=read();
		cnt=0;memset(vis,0,sizeof vis);
		int now_x;
		for(int i=1;i<=N;i++)if(!vis[i]){
			now_x=i;int num=0;
			while(!vis[now_x]){vis[now_x]=1;now_x=a[now_x];num++;};
			size[++cnt]=num;
		}
		ans=Fc[N-cnt];
		for(int i=1;i<=cnt;i++){
			LL inv=Pow(Fc[size[i]-1],TT-2);
			ans=ans*F[size[i]]%TT*inv%TT;
		}
		printf("%lld
",ans);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/martian148/p/13860848.html