[CH3602][数论]Counting Swaps

本文探讨了如何通过最少次数的元素交换将一个给定的排列转换成单调递增的序列,并提供了具体的算法实现,包括如何计算所有可能的操作序列及其数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

描述

给定一个 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/2T(x,y)=n/2 n为偶数且x=y
T(x,y)=nT(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=nT(x,y)∗f[x]∗f[y]∗(n−2)!(x−1)!(y−1)!

最终答案也可以用一个多重集的排列给出
对于k个长度分别为l1,l2,...,lkl1,l2,...,lk的环,有
ans=f[l1]f[l2]...f[lk](nk)!(l11)!(l21)!...(lk1)!ans=∏f[l1]∗f[l2]∗...∗f[lk]∗(n−k)!(l1−1)!(l2−1)!...(lk−1)!

递推复杂度O(n2)O(n2)
我们把f的前几项求出来找规律可以发现f[n]=nn2f[n]=nn−2
如此复杂度降为O(nlogn)O(nlog⁡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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值