【容斥原理】 Counting swaps

传送门

题意

给定一个排列\(p_{1},p_{2},\dots,p_{n}\),规定操作是选择两个数进行交换,设将排列变成\(1,2,/dots,n\)需要最少次数需要m次交换,求出有多少次不同的操作达到升序排列的目标,对答案模\(10^{9} + 9\)

数据范围

\(1\leq n\leq 10^{5}\)

题解

对于一个排列\(p_{1},p_{2},\dots,p_{n}\),每个\(i\)\(p_{i}\)连一条边,那么可以得到\(n\)个点的无\(n\)条边的图,
并且图是由若干边组成。
引理:将一个长度为\(n\)的环边成\(n\)个自环,最少需要\(n-1\)次交换。
其中长度为n的环必然满足这样\(2,3,/\dots,n,1\) 的大小关系
引理的简单证明:
长度为\(2\)的环, 显然需要2次交换,
假设\(\forall k<n\)长度为\(k\)的环交换变成升序的最少交换都是\(k-1\)
长度为n的环\(v_{1} \rightarrow v_{2} \rightarrow \cdots \rightarrow v_{n} \rightarrow v_{1}\)
交换\(v_{i}\)\(v_{j}\)\((i<j)\)的出边即\(v_{i+1}\)\(v_{j+1}\)后,环被拆成了

\[v_{i+2}\rightarrow v_{i+3} \rightarrow \dots \rightarrow v_{j} \rightarrow v_{i+1}\rightarrow v_{i+2} \]

\[v_{1} \rightarrow v_{2} \rightarrow \dots\rightarrow v_{i} \rightarrow v_{j+1} \rightarrow v_{j+2} \rightarrow \dots \rightarrow v_{n} \rightarrow v_{1} \]

这两个环,两个环的长度分别是\(j-i\)\(n-(j-i)\),
两个环的最少交换次数是\((j-i-1)\)\(n-(j-i)-1\) 加上刚才的一次交换即\(n-1\)
数学归纳法得证
\(f_{n}\)表示将长度为\(n\)的环变成\(n\)自环的不同操作数量,在变成自环的过程中
可以将该环拆成长度为\(x\)\(y\)的两个不同的环,\(x+y=n\)
\(g(x,y)\)表示有多少种交换方法可以把长度为\(n\)的环拆分成长度为\(x,y\)的两个环
那么可以得到

\[g(x, y)\left\{\begin{array}{ll}\frac{n}{2} & n \text { is even } \wedge x=y \\ n & \text { otherwise }\end{array}\right. \]

\(x\)\(y\)变成自环的不同的交换个数分别为\(f_{x}\)\(f_{y}\),交换次数分别是\(x-1\)\(y-1\),两个分别是独立的所以只需要计算交错排列的,
\(x\)\(y\)内部的交换在\(f_{x}\)\(f_{y}\)递归中\(g\)所统计所以只需要执行\(x\)\(y\)的先后排列即可。
假设排列\(p_{1},p_{2},\dots,p_{n}\)中有长度为\(l_{1},l_{2},\dots,l_{k}\)\(k\)个环组成,\(l_{1}+l_{2}+\dots+l_{k}=n\),那么最终的答案就是

\[Ans=\left(\prod_{i=1}^{k} f\left[l_{i}\right]\right) *\left(\frac{(n-k) !}{\prod_{i=1}^{k}\left(l_{i}-1\right) !}\right) \]

\(10^{9} + 9\)是质数快速幂处理逆元即可,求\(f\)\(O(n)\)的,复杂度是\(O(n \times k)\)
打表前10项得到

\[1\quad 1 \quad 3 \quad 16 \quad 125 \quad 1296 \quad 16807 \quad 262144 \quad 4782969 \quad 100000000 \]

带入OEIS中可以得到通项公式为

\[f_{n}=n^{n-2} \]

这样可以将求\(f\)复杂度降到\(O(logn)\)

Code

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+10,mod=1e9+9;
ll jc[N];
bool v[N];
ll p[N];
ll quick_pow(ll a,ll b){
    ll res=1;
    while(b){
        if(b&1) res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}
int main(){
    jc[0]=1;
    for(int i=1;i<=100000;i++){
        jc[i]=jc[i-1]*i%mod;
    }
    int t;
    cin>>t;
    while(t--){
        int n;
        cin>>n;
        for (int i = 1; i <= n; i++) scanf("%lld",&p[i]);
        fill(v,v+n+1,0);
        int cnt = 0;
        ll ans = 1;
        for (int i = 1; i <= n; i++) {
            if (v[i]) continue;
            int len = 1;//环的长度
            v[i] = 1;
            for (int j = p[i]; j != i; j = p[j]) v[j] = 1, len++;
            cnt++;
            ans = ans * (len == 1 ? 1 : quick_pow(len, len - 2)) % mod;
            ans = ans * quick_pow(jc[len - 1], mod - 2) % mod;
        }
        ans = ans * jc[n - cnt] % mod;
        printf("%lld\n",ans);
    }
    return 0;
}
posted @ 2021-03-06 21:09  Hyx'  阅读(125)  评论(0)    收藏  举报