当前位置: 代码迷 >> 综合 >> [CH3602][数论]Counting Swaps
  详细解决方案

[CH3602][数论]Counting Swaps

热度:64   发布时间:2023-12-19 05:18:44.0

描述

给定一个 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 1

4
2 1 4 3

2
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;
}