• 康拓展开及逆康拓展开


    概念:

    康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,因此是可逆的。

    康拓展开:

    给定一个全排列序列,求该序列是所有全排列序列中字典序第几的序列

    公式如下:

    在这里插入图片描述
    其中, a[i] 为整数,并且 0<=a[i]<i,1<=i<=n。

    • a[i]表示位于位置i后面的数小于a[i]值的个数。
    • X为小于该排列的个数,所以该排列的次序应该是X+1。

    在这里插入图片描述

    康拓展开代码:

        cin>>n;
        fac[0]=1;
        for(int i=1;i<=n;i++)//预处理阶乘 
            fac[i]=fac[i-1]*i;
        int ans=0;
        for(int i=1;i<=n;i++) cin>>a[i];
        for(int i=1;i<=n;i++){
            int k=0;
            for(int j=i+1;j<=n;j++) 
                if(a[j]<a[i])k++; ///统计位于a[i]后且比a[i]小的数的个数 
            ans+=k*fac[n-i];
        }
        cout<<ans+1<<endl; ///注意,记得+1

    逆康拓展开:

    给定全排列大小n,字典序k,求字典序为k的排列。

    在这里插入图片描述
    逆康拓展开代码:

    vector<int>y;
    void ni_Cantor(int len,int num)
    {
         num=num-1; ///注意
        vector<int>a;
        for(int i=1;i<=len;i++)
            a.push_back(i);
          
        for(int i=1;i<=len;i++){
            int t=num/ans[len-i];
            num=num%ans[len-i];
            y.push_back(a[t]); ///剩余数里第t+1个数为当前位 
            a.erase(a.begin()+t); ///删除选做当前位的数
        }
    }

    应用:

    1. 康托展开是一个数组到一个数的映射,可以应用于hash中进行空间压缩。例如,在八数码问题中,我们可以把一种排列状态压缩成一个整数存放在数组中。(获取排列的id,构建hash表)
    2. 计算关于排列序列的问题

    康拓展开及逆康拓展开:

    #include<iostream>
    #define _ ios::sync_with_stdio(false);
    using namespace std;
    
    int ans[12]; ///存储阶乘
    void factorial(int q) ///预处理出0!~q!
    {
        ans[0]=1;
        for(int i=1;i<=q;i++)
            ans[i]=ans[i-1]*i;
    }
    
    /*康拓展开*/ 
    int Cantor(int n,int x[]) ///n表示n个数的排列,x[]为传递过来的排列数组
    {
        int cnt=0,sum;
        for(int i=1;i<=n;i++){
            sum=0;
            for(int j=i+1;j<=n;j++){
                if(x[j]<x[i])
                    sum++;
            }
            cnt+=sum*ans[n-i];
        }
        return cnt+1;
    }
    
    /*逆康拓展开*/
    int* ni_Cantor(int len,int num,int y[]) ///len表示len个数的排列,num表示次序,y[]存储生成的排列数组
    {
        bool book[len+2]={0};
        int sum2,k=0,t;
        num=num-1;
        for(int i=1;i<=len;i++){
            sum2=0;
            t=num/ans[len-i];
            num=num%ans[len-i];
            for(int j=1;j<=len;j++) ///对比上面的逆康拓展开做法,利用vector的特性,而不需要去for嵌套,降低了复杂度
                if(!book[j]){
                    sum2++;
                    if(sum2==t+1){
                        y[k++]=j;
                        book[j]=1;
                        break;
                    }
                }
        }
        return y;
    }
    
    int main(){_
    
        factorial(10); ///预处理出0!~10!
        int T,a;
        cin>>T; ///T个测试用例
        while(T--)
        {
            int x[15]={},y[15],n,a,m;
            
            cin>>a; 
            ///a为0时,输出该排列的次序编号;
            ///a为1时,输出n个正整数,表示这个次序对应的一个全排列(每个数字后面跟一个空格)。
            
            if(a==0){
                cin>>n; ///0<n<10
                for(int i=1;i<=n;i++)
                    cin>>x[i];
                cout<<Cantor(n,x)<<endl;
            }
            else if(a==1){
                cin>>n>>m; ///n表示n个数的全排列,m表示次序编号为m的全排列
                ni_Cantor(n,m,y);
                for(int i=0;i<n;i++)
                    cout<<y[i]<<' ';
                cout<<endl;
            }
        }
        return 0;
    }
    
    
  • 相关阅读:
    牛客网每日一练
    牛客网每日一练
    牛客网每日一练
    牛客网每日一练
    牛客网每日一练
    牛客网每日一练
    牛客网每日一练
    牛客网每日一练
    牛客网每日一练
    牛客网每日一练
  • 原文地址:https://www.cnblogs.com/HOLLAY/p/11374853.html
Copyright © 2020-2023  润新知