[CH3602][数论]Counting Swaps

描述

给定一个 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](n2)!(x1)!(y1)! 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](nk)!(l11)!(l21)!...(lk1)! 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]=nn2 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值