康托展开与逆康托展开

前言

这个东西也算比较常见吧,所以来记录下

作用

康托展开的实质是计算当前排列在所有由小到大全排列中的顺序

逆康托展开即已知在所有由小到大全排列中的顺序求排列

介绍

首先康托展开的公式

(x=a_i(n-1)!+a_{i-1}(n-2)!...a_{1}(0)!)

(a_i)表示原数的第(i)位在当前未出现的元素中有几个比他小

感觉这个证明挺简单的,不做说明

那么如何计算(a_i)呢,首先可以暴力(O(n^2))求出所有(a_i)

显然要优化

设当前值为(s),且其对应的(a_i)(t),说明未出现的元素有(t)个元素比(s)

那么说明在已经出现的元素中有(s-1-t)个元素比(s)

利用线段求前面有多少个元素比自己小即可

实例

题目链接

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int maxn=1e6+5,mod=998244353,inf=0x3f3f3f3f;
int n,a[maxn];
int tree[maxn<<2];
int fac[maxn];
int query(int node,int L,int R,int l,int r){
    if(L>R) return 0;
    if(L<=l&&r<=R){
        return tree[node];
    }
    int mid=(l+r)>>1,sum=0;
    if(mid>=L) sum+=query(node<<1,L,R,l,mid);
    if(mid<R) sum+=query(node<<1|1,L,R,mid+1,r);
    return sum;
}
void update(int node,int l,int r,int pos){
    if(l==r){
        tree[node]=1;
        return ;
    }
    int mid=(l+r)>>1;
    if(mid>=pos) update(node<<1,l,mid,pos);
    else update(node<<1|1,mid+1,r,pos);
    tree[node]=tree[node<<1]+tree[node<<1|1];
}
int main(){
    scanf("%d",&n);
    fac[0]=1;
    for(int i=1;i<=n;i++){
        fac[i]=1ll*fac[i-1]*i%mod;
    }
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    ll ans=1;
    for(int i=1;i<=n;i++){
        ans=(ans+1ll*(a[i]-query(1,1,a[i]-1,1,n)-1)*fac[n-i])%mod;
        update(1,1,n,a[i]);
    }
    printf("%lld
",ans);
    return 0;
}

逆康托展开其实就是一个相反的操作

实例

题目链接

题目思路

就是逆操作,权值线段树上二分即可

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int maxn=1e6+5,mod=998244353,inf=0x3f3f3f3f;
int n,a[maxn];
int tree[maxn<<2];
int query(int node,int l,int r,int val){
    tree[node]--;
    if(l==r){
        return l;
    }
    int mid=(l+r)>>1,ans;
    if(tree[node<<1]>=val)  ans=query(node<<1,l,mid,val);
    else ans=query(node<<1|1,mid+1,r,val-tree[node<<1]);
    return ans;
}
void build(int node,int l,int r){
    if(l==r){
        tree[node]=1;
        return ;
    }
    int mid=(l+r)/2;
    build(node<<1,l,mid);
    build(node<<1|1,mid+1,r);
    tree[node]=tree[node<<1]+tree[node<<1|1];
}
int main(){
    int _; scanf("%d",&_);
    while(_--){
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
        }
        build(1,1,n);
        for(int i=1;i<=n;i++){
            printf("%d%c",query(1,1,n,a[i]+1),i==n?'
':' ');
        }
    }
    return 0;
}

卷也卷不过,躺又躺不平
原文地址:https://www.cnblogs.com/hunxuewangzi/p/14682481.html