zoukankan      html  css  js  c++  java
  • 康托展开(自然数到全排列的映射)

    定义

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

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

    时间复杂度

    康托展开可以在 (O(n^2)) 的复杂度内求出一个排列的排名,在用到树状数组优化时可以做到 (O(nlog 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 imes 3!)

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

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

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

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

    如果我们知道一个排列的排名,就可以推出这个排列。因为 (4!) 是严格大于 (3 imes 3!+2 imes 2!+1 imes 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
    ",getrank(a,n));
            }
        }
    }
    
  • 相关阅读:
    Java数据结构与算法之---求两个数的最大公约数(欧几里得算法)
    Linux下面配置文件~/.bash_profile
    Java数据结构之回溯算法的递归应用迷宫的路径问题
    Java数据结构之对称矩阵的压缩算法---
    Java数据结构之字符串模式匹配算法---KMP算法2
    Java数据结构之字符串模式匹配算法---KMP算法
    Java数据结构之字符串模式匹配算法---Brute-Force算法
    Java数据结构之表的增删对比---ArrayList与LinkedList之一
    Java数据结构之队列的实现以及队列的应用之----简单生产者消费者应用
    Java堆栈的应用1----------堆栈的自定义实现以及括号匹配算法的Java实现
  • 原文地址:https://www.cnblogs.com/ucprer/p/14150665.html
Copyright © 2011-2022 走看看