1. 程式人生 > 實用技巧 >題解 P4778 【Counting swaps】

題解 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)=\begin{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\n",ans);
	}
	return 0;
}