題解 P4778 【Counting swaps】
Link
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\)
兩者的長度分別是\(j-i\)和\(n-(j-i)\)。把兩者分別拆分成自環的最小交換次數為\(j-i-1\)和\(n-(j-i)-1\)兩者相加是\(n-2\),加上剛才\(v_i\)和\(v_j\)的交換,總共需要\(n-1\)次交換。最後通過數學歸納法可知,原命題成立。
證畢
設\(F_n\)表示用最少步數把一個長度為\(n\)
另外,兩者各自變為自環的方法數為\(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;
}