描述
给定一个 1~n 的排列 p_1,p_2,…,p_n,可进行若干次操作,每次选择两个整数 x,y,交换 p_x,p_y。设把
p_1,p_2,…,p_n 变成单调递增的排列 1,2,…,n 至少需要 m 次交换。求有多少种操作方法可以只用 m
次交换达到上述目标。因为结果可能很大,你只需要输出对 10^9+9 取模之后的值。1≤n≤10^5。 例如排列 2,3,1
至少需要2次交换才能变为 1,2,3。操作方法共有3种,分别是: 先交换数字2,3,变成 3,2,1,再交换数字3,1,变成 1,2,3。
先交换数字2,1,变成 1,3,2,再交换数字3,2,变成 1,2,3。 先交换数字3,1,变成 2,1,3,再交换数字2,1,变成
1,2,3。 You are given a permutation p1, …, pn of the numbers 1 through
n. In each step you can choose two numbers x < y and swap px with py.Let m be the minimum number of such swaps needed to sort the given
permutation. Compute the number of different sequences of exactly m
swaps that sort the given permutation. Since this number may be large,
compute it modulo 109 + 9.
输入格式
The first line of the input file contains an integer t specifying the
number of test cases. Each test case is preceded by a blank line.Each test case consists of two lines. The first line contains the
integer n. The second line contains the sequence p1, …, pn: a
permutation of 1, …, n.In the easy subproblem C1, 1 ≤ n ≤ 10.
In the hard subproblem C2, 1 ≤ n ≤ 105.
输出格式
For each test case, output a single line with a single integer: x
mod(10^9+9), where x is the number of ways to sort the given sequence
using as few swaps as possible.
样例输入
3
3
2 3 14
2 1 4 32
1 2
样例输出
3
2
1
题解
我的数学还是一如既往的烂..
对于每个位置i,我们向他应该填的数所在的位置p[i]连一条边
如此会出来一些环,我们的目的是将这些环拆成n个自环
对于一个长度为n的环,我们发现要把他拆成n个自环至少需要n-1次操作
设T(x,y)表示将长度为n的环拆成长度分别为x,y的环的方案数,设f[n]表示将长度为n的环拆成n个自环的方案数
画图可知
T(x,y)=n/2 T ( x , y ) = n / 2 n为偶数且x=y
T(x,y)=n T ( x , y ) = n otherwise
对于长度为x的环的操作全部看成0,长度为y的环的操作全部看成1,进行多重集的排列。可以发现这对应出的就是长度为n的环要拆成n个自环的操作方案
根据多重集的排列公式有
f[n]=∑x+y=nT(x,y)∗f[x]∗f[y]∗(n−2)!(x−1)!(y−1)! f [ n ] = ∑ x + y = n T ( x , y ) ∗ f [ x ] ∗ f [ y ] ∗ ( n − 2 ) ! ( x − 1 ) ! ( y − 1 ) !
最终答案也可以用一个多重集的排列给出
对于k个长度分别为 l1,l2,...,lk l 1 , l 2 , . . . , l k 的环,有
ans=∏f[l1]∗f[l2]∗...∗f[lk]∗(n−k)!(l1−1)!(l2−1)!...(lk−1)! a n s = ∏ f [ l 1 ] ∗ f [ l 2 ] ∗ . . . ∗ f [ l k ] ∗ ( n − k ) ! ( l 1 − 1 ) ! ( l 2 − 1 ) ! . . . ( l k − 1 ) !
递推复杂度 O(n2) O ( n 2 )
我们把f的前几项求出来找规律可以发现 f[n]=nn−2 f [ n ] = n n − 2
如此复杂度降为 O(nlogn) O ( n log n )
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long LL;
const LL mod=1e9+9;
struct node{int x,y,next;}a[110000];int len,last[110000];
void ins(int x,int y){len++;a[len].x=x;a[len].y=y;a[len].next=last[x];last[x]=len;}
bool v[110000];
int sta[110000],belong[110000],low[110000],dfn[110000],sum[110000];
int id,cnt,tp;
void tarjan(int x)
{
dfn[x]=low[x]=++id;sta[++tp]=x;
v[x]=true;
for(int k=last[x];k;k=a[k].next)
{
int y=a[k].y;
if(dfn[y]==-1)
{
tarjan(y);
low[x]=min(low[x],low[y]);
}
else
{
if(v[y])low[x]=min(low[x],dfn[y]);
}
}
if(dfn[x]==low[x])
{
int i;cnt++;
do
{
i=sta[tp--];
belong[i]=cnt;
sum[cnt]++;
v[i]=false;
}while(i!=x);
}
}
LL pow_mod(LL a,LL b)
{
LL ret=1;
while(b)
{
if(b&1)ret=ret*a%mod;
a=a*a%mod;b>>=1;
}
return ret;
}
LL inv[110000];
int n,p[110000];
int main()
{
int T;scanf("%d",&T);
LL tmp=1;
for(LL i=1;i<=100000;i++)
{
tmp=tmp*i%mod;
inv[i]=pow_mod(tmp,mod-2);
}
while(T--)
{
len=0;memset(last,0,sizeof(last));
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&p[i]),ins(p[i],i);
id=cnt=tp=0;
memset(sum,0,sizeof(sum));
memset(dfn,-1,sizeof(dfn));
memset(v,false,sizeof(v));
for(int i=1;i<=n;i++)if(dfn[i]==-1)tarjan(i);
LL ans=1;
for(int i=1;i<=cnt;i++)if(sum[i]!=1)ans=ans*pow_mod(sum[i],sum[i]-2)%mod;
for(LL i=1;i<=n-cnt;i++)ans=ans*i%mod;
for(int i=1;i<=cnt;i++)if(sum[i]!=1)ans=ans*inv[sum[i]-1]%mod;
printf("%lld\n",ans);
}
return 0;
}