zoukankan      html  css  js  c++  java
  • 基本算法——康托展开与逆康托展开

    含义

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

    原理

      X = s1(n-1)! + s2(n-2)! + s3(n-3)! + …… + sn-1 * 1! + sn * 0! 

      其中si表示在第i位右边比ai小的数的个数。

      我们现在用sl表示第i位左边比ai小的数的个数,sr表示第i位右边比ai小的数的个数,显然可以得到如下等式:
      ai = sl + sr + 1

      故公式中的si可以用上述等式计算:sr = ai - sl -1

      依照上述原理,则{1,2,3,4,5}的一种全排列{3,4,1,5,2}可以映射为{2,2,0,1,0}

      根据公式,该集合实际上表示的就是一个变进制数。

      简便计算:X = ((s1 * (n-1) + s2) * (n-3) + s3) * (n-3) + …… 

    康托展开

      暴力  O(n2):

      为了便于讲解下面的线段树优化,此处选择维护vis数组的方式(当然直接比大小也是一样的)。

      vis[j]用以记录j是否已出现,未出现为0,已出现为1。故a[i]左边比其小的数的个数就是vis[j]的和(j<a[i])。

      计算ans时,由于最后一位固定是0,故只需计算到n-1位即可。

      而所有全排列中比该序列小的有ans个,故该序列排在第ans+1位。

      代码如下:

    int Power_Cantor()
    {
        int ans=0;
        for(int i=1;i<=n-1;i++)
        {
            scanf("%d",&a[i]);
            int sum=0;
            for(int j=1;j<=a[i];j++)sum+=vis[j];
            vis[a[i]]=1;
            a[i]-=sum+1;
            ans=(ans+a[i])*(n-i);
        }
        return ans+1;
    }

      线段树优化  O(nlogn):

      我们用线段树结构存储vis数组,即可实现logn时间复杂度内求出结果。

      代码如下:

    int Cantor()
    {
        int ans=0;
        for(int i=1;i<=n-1;i++)
        {
            int now=a[i];
            now-=query(1,1,n,1,a[i])+1;
            update(1,1,n,a[i],1);
            ans=(ans+now)*(n-i);
        }
        return ans+1;
    }

    逆康托展开

      首先是将所给的排列位数转变为我们所需的变进制数:

      仍以{3,4,1,5,2}为例,其康托展开值为61:

      用 61 / 4! = 2余13,则a[1] = 2,即首位右边比首位小的数有2个,所以首位为3。

      用 13 / 3! = 2余1,则a[2] = 2,即在第二位之后小于第二位的数有2个,所以第二位为4。

      用 1 / 2! = 0余1,则a[3] = 0,即在第三位之后没有小于第三位的数,所以第三位为1。

      用 1 / 1! = 1余0,则a[4] = 1,即在第四位之后小于第四位的数有1个,所以第四位为5。

      最后一位自然就是剩下的数2。

      通过以上分析,所求排列组合为 34152。

      依然是用线段树维护vis数组,建树时每一位都先赋值为1,表示所有数均未出现。

      设对于当前这一位i,变进制数为a[i],要求的数为x。目标是要在未出现的数中找比x小的数的个数为a[i]个的数的位置,相当于在区间[1,x]中找a[i]+1。

      此处将a[i]+1记为s[i]。用二分查找x的位置:对于每一位s[i],先求出左子树比x小的数的个数为sum,再看s是否有s[i]个:若有,说明结果在左子树,则继续往左子树找s[i];若没有,则往右子树找s[i] - sum。

      查找完后将vis[x]的值修改为0。

      代码如下:

    int search(int num)
    {
        int l=1,r=n;
        while(l<r)
        {
            int mid=l+r>>1;
            int find=query(1,1,n,l,mid);
            
            if(find>=num) r=mid;
            else{
                l=mid+1;
                num-=find;
            }
        }
        return r;
    }
    
    void R_Cantor(int num)
    {
        num--;  
        memset(a,0,sizeof a);
        build_tree(1,1,n);
        for(int i=1;i<=n;i++)
        {
            a[i]=num/(jc[n-i]);
            a[i]=search(a[i]+1);
            update(1,1,n,a[i],0);
            num%=jc[n-i];
        }
        
        for(int i=1;i<=n;i++)printf("%d ",a[i]);
        puts("");    
    }

    附完整代码

    #include <algorithm>
    #include <cstring>
    #include <cstdio>
    #include <iostream>
    using namespace std;
    const int N = 1e5+10;
    
    int n;
    int a[N],tree[3*N];
    int jc[10]={1,1,2,6,24,120,720,5040,40320,362880}; 
    
    void out()
    {
        for(int i=1;i<=14;i++)
        {
            printf("tree[%d] = %d
    ",i,tree[i]);
        }
        puts("");
    }
    
    void build_tree(int node,int start,int end)
    {
        if(start==end)
        {
            tree[node]=1;
            return;
        }
        int mid=start+end>>1;
        int left=2*node;
        int right=2*node+1;
        build_tree(left,start,mid);
        build_tree(right,mid+1,end);
        tree[node]=tree[left]+tree[right];
    }
    
    void update(int node,int start,int end,int idx,int val)
    {
        if(start==end)
        {
            tree[node]=val;
            return;
        }
        int mid=start+end>>1;
        int left=2*node;
        int right=2*node+1;
        if(idx<=mid)update(left,start,mid,idx,val);
        else update(right,mid+1,end,idx,val);
        tree[node]=tree[left]+tree[right];
    }
    
    int query(int node,int start,int end,int l,int r)
    {
        if(end<l || start>r)return 0;
        else if(start>=l && end<=r)return tree[node];
        
        int mid=start+end>>1;
        int left=2*node;
        int right=2*node+1;
        
        return query(left,start,mid,l,r) + query(right,mid+1,end,l,r);
    }
    
    int Cantor()
    {
        int ans=0;
        for(int i=1;i<=n-1;i++)
        {
            int now=a[i];
            now-=query(1,1,n,1,a[i])+1;
            update(1,1,n,a[i],1);
            ans=(ans+now)*(n-i);
        }
        return ans+1;  //所有全排列中比该序列小的有ans个,故该序列排在第ans+1位 
    }
    
    int search(int num)
    {
        int l=1,r=n;
        while(l<r)
        {
            int mid=l+r>>1;
            int find=query(1,1,n,l,mid);
            
            // 先看左子树中未出现的比要求的数小的数的个数够不够num个 
            // 若足够,则继续往左子树找num 
            // 若不够,则继续往右子树找 (num-已找到的个数) 
            
            if(find>=num) r=mid;
            else{
                l=mid+1;
                num-=find;
            }
        }
        return r;
    }
    
    void R_Cantor(int num)
    {
        num--; //该序列排在第num位,故比其小的全排列有num-1个。 
        memset(a,0,sizeof a);
        build_tree(1,1,n);
        for(int i=1;i<=n;i++)
        {
            a[i]=num/(jc[n-i]);
            a[i]=search(a[i]+1);
            update(1,1,n,a[i],0);
            num%=jc[n-i];
        }
        
        for(int i=1;i<=n;i++)printf("%d ",a[i]);
        puts("");    
    }
    
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)scanf("%d",&a[i]);
        
        memset(tree,0,sizeof tree);
        
        int ans=Cantor();
        
    //    R_Cantor(62);
    
        printf("%d
    ",ans);
        return 0;
    }
  • 相关阅读:
    解决git推不上去1
    django中CBV源码分析
    Form和ModelForm组件
    jquery操作cookie
    django中的中间件
    django中ORM中锁和事务
    django_ajax
    docker安装jenkins 容器,集成python环境
    支付宝第三方支付
    redis基本使用
  • 原文地址:https://www.cnblogs.com/ninedream/p/11521141.html
Copyright © 2011-2022 走看看