欢迎大家访问我的老师的OJ———caioj.cn
题面描述
思路
对于一个排列 ,如果从每个 向 连一条边,那么可以得到 个点 条边的图,并且这张图由若干个环构成。
列入排列
对应下图,由
和5三个环。
显然,我们最后要得到
个自环,即排列
.
引理:
把一个长度为n的环变成n个自环最少需要n-1次交换操作。
证明:
显然,每一次操作,最多只能将一个环变成两个环,所以将一个长度为n的环变成n个自环,
最少也需要n-1次交换操作。
证毕。
设 表示用最少的步数把一个长度为 的环变成 个自环,共有多少种方法。把一个长度为 的环变成 个自环的过程中,把该环拆成长度为 和 的两个环,其中 。设 表示有多少种交换方法可以把长度为n的环变成长度为 和 的两个环,下面我们进行分类讨论:
当n为偶数并且x=y时,
如图,排列4,1,2,3构成一个长度为4的环,我们要将其分成2个长度为2的环。
有2种方法:
则 ( 为偶数并且 )
当 是奇数或 时, ,如何构造参照上图(在下太懒了)
综上所述,
另外,二者各自变为自环的方法书为 和 ,步数为 和 。
根据多重集的排列数、加法原理和乘法原理:
相当于有
个操作,其中有
个为1,
个为0,
其实相当于求多重集
的全排列数,即
如果最初的排列
,由长度为
的k个环构成,其中
,根据多项式排列数,那么最终答案就是:
稍微解释一下为什么还要乘 ,因为我们要 步才能变成n个自环,所以相当于我们有 个操作,操作如果换一下顺序,就有 种,但其中有 种属于同一类,因为相同操作不分先后。
我们可以递推前几项找规律,得到通项公式
AC code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#define ll long long
#define gc getchar()
using namespace std;
const ll mod=1e9+9;
const int N=1e5+10;
inline void qr(int &x)
{
x=0;int f=1;char c=gc;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc;}
while(c>='0'&&c<='9'){x=x*10+(c^48);c=gc;}
x*=f;
}
void qw(ll x)
{
if(x/10)qw(x/10);
putchar(x%10+48);
}
inline ll pow_mod(ll a,ll b)
{
ll ans=1;a%=mod;
while(b)
{
if(b&1)ans=ans*a%mod;
b>>=1;a=a*a%mod;
}
return ans;
}
ll c[N];int p[N];bool v[N];
int main()
{
c[0]=1;
for(int i=1;i<=N;i++)c[i]=c[i-1]*i%mod;
int t;qr(t);
while(t--)
{
int n;qr(n);ll ans=1;
for(int i=1;i<=n;i++)qr(p[i]),v[i]=0;
int cnt=0;
for(int i=1;i<=n;i++)
{
if(v[i])continue;
v[i]=1;
int len=1;cnt++;
for(int j=p[i];j!=i;j=p[j])++len,v[j]=1;
if(len>2)ans=ans*pow_mod(len,len-2)%mod;
ans=ans*pow_mod(c[len-1],mod-2)%mod;
}
ans=ans*c[n-cnt]%mod;
qw(ans);puts("");
}
return 0;
}