康托展开

康托展开和逆康托展开

康托展开和逆康托展开

前言

康托展开和逆康托展开是针对全排列问题的。

全排列:对于一个长度为 $N$ 的数组 $a$ , 满足 $1≤ai≤N$ 并且 各个元素互不相同。

数组顺序
1 2 3 4 51
1 2 3 5 42
1 2 5 3 43

原理

康托展开的例子

如果给一个$N=5$ 的数组$[3,2,5,4,1]$

对于第一个数据3 , 后面比它小的数据有 2 个,所以 以 [1] 、[2] 为开头的全排列,都在它前面,$2*4!$

对于第二个数据2 , 后面比它小的数据有 1 个,所以 以 [3,1] 为开头的全排列,都在它前面,$1*3!$

对于第三个数据5,后面比它小的数据有 2 个,所以 以 [3,2,1]、[3,2,4] 开头的全排列,都在它前面,$2*2!$

对于第四个数据4,后面比它小的数据有 1 个,所以 以 [3,2,5,1] 开头的全排列,都在它前面,$1*1!$

对于第五个数据1,后面比它小的数据有 0 个,所以没有全排列在它前面,$0$

$$ 2*4!+1*3!+2*2!+1*1!+0*0!+1 $$

因为本身的顺序也算在其中,所以额外加1

代码

使用数状数组来进行统计 $ai$ 后面有多少比它小的元素。

#include 
#include 
#include 
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int MAXN = 1e6+5;
ll fac[MAXN];
ll node[MAXN];
inline void Add(int i,int N){
    while(i<=N){
        node[i]-=1;
        i+=i&-i;
    }
}
inline ll Sum(int i){
    int s=0;
    while(i){
        s+=node[i];
        i-=i&(-i);
    }
    return s;
}
inline void Init(int N){
    ios::sync_with_stdio(false);
    cin.tie(0);
    fac[0]=1;
    memset(node,0,sizeof(node));
    for(ll i=1;i<=1e6;++i){
        fac[i]=(fac[i-1]*i)%mod;
        node[i]=i&-i;
    }
}
int main(){
    int N,x;
    ll sum=1;
    cin>>N;
    Init(N);
    for(int i=0;i>x;
        Add(x,N);
        sum+=Sum(x)*fac[N-i-1];
        sum%=mod;
    }
    cout<

 

posted @ 2020-03-23 21:02  VagrantAC  阅读(117)  评论(0编辑  收藏  举报