康托展开(自然数到全排列的映射)

定义

康托展开可以用来求一个 \(1\sim n\) 的任意排列的排名。

\(1\sim n\) 的所有排列按字典序排序,这个排列的位次就是它的排名。

时间复杂度

康托展开可以在 \(O(n^2)\) 的复杂度内求出一个排列的排名,在用到树状数组优化时可以做到 \(O(n\log n)\)

全排列到自然数

因为排列是按字典序排名的,因此越靠前的数字优先级越高。也就是说如果两个排列的某一位之前的数字都相同,那么如果这一位如果不相同,就按这一位排序。

比如 \(4\) 的排列, \([2,3,1,4]<[2,3,4,1]\) ,因为在第 \(3\) 位出现不同,则 \([2,3,1,4]\) 的排名在 \([2,3,4,1]\) 前面。

举例:

我们知道长为 \(5\) 的排列 \([2,5,3,4,1]\) 大于以 \(1\) 为第一位的任何排列,以 \(1\) 为第一位的 \(5\) 的排列有 \(4!\) 种。这是非常好理解的。但是我们对第二位的 \(5\) 而言,它大于 第一位与这个排列相同的,而这一位比 \(5\) 小的 所有排列。不过我们要注意的是,这一位不仅要比 \(5\) 小,还要满足没有在当前排列的前面出现过,不然统计就重复了。因此这一位为 \(1,3\)\(4\) ,第一位为 \(2\) 的所有排列都比它要小,数量为 \(3\times 3!\)

按照这样统计下去,答案就是 \(1+4!+3\times 3!+2!+1=46\) 。注意我们统计的是排名,因此最前面要 \(+1\)

注意到我们每次要用到 当前有多少个小于它的数还没有出现 ,这里用树状数组统计比它小的数出现过的次数就可以了。

自然数到全排列(逆康托展开)

因为排列的排名和排列是一一对应的,所以康托展开满足双射关系,是可逆的。可以通过类似上面的过程倒推回来。

如果我们知道一个排列的排名,就可以推出这个排列。因为 \(4!\) 是严格大于 \(3\times 3!+2\times 2!+1\times 1!\) 的,所以可以认为对于长度为 \(5\) 的排列,排名 \(x\) 除以 \(4!\) 向下取整就是有多少个数小于这个排列的第一位。

代码

/*
输入p x,输出第x个排列;输入Q p1,p2..pn,输出排列的排名
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int a[25],ans[25];
ll fac[25];
void init(int n){
    fac[1]=1;
    for(ll i=2;i<=n;i++){
        fac[i]=fac[i-1]*i;
    }
}
ll getrank(int *a,int n){
    ll ans=1;
    for(int i=1;i<=n;i++){
        int cnt=a[i]-1;
        for(int j=1;j<=i-1;j++){
            if(a[j]<a[i])cnt--;
        }
        ans+=cnt*fac[n-i];
    }
    return ans;
}
void getpermutation(ll p,int n){
    vector<int>vis(n+1,0);
    p--;
    for(int i=1;i<=n;i++){
        ll cnt=i!=n?p/fac[n-i]:0;
        p-=cnt*fac[n-i];
        int res=0;
        for(int j=1;j<=n&&cnt>0;j++){
            if(!vis[j]){
                res=j;cnt--;
            }
        }
        res++;
        while(vis[res])res++;
        ans[i]=res;vis[res]=1;
    }
}
int main () {
    int n,k;
    scanf("%d%d",&n,&k);
    init(n);
    while(k--){
        char c;
        scanf(" %c",&c);
        if(c=='P'){
            ll p;
            scanf("%lld",&p);
            getpermutation(p,n);
            for(int i=1;i<=n;i++){
                if(i>1)printf(" ");
                printf("%d",ans[i]);
            }puts("");
        }
        else{
            for(int i=1;i<=n;i++){
                scanf("%d",&a[i]);
            }
            printf("%lld\n",getrank(a,n));
        }
    }
}
posted @ 2020-12-17 17:08  UCPRER  阅读(157)  评论(0编辑  收藏  举报