康托展开和逆展开

康托展开:

对于1~n的所有排列,要确定某个排列是字典序中第几个排列,可用康托展开。这个技巧在做对排列的hash时十分有用,因为不需要使用set来记录那些大于int最大值的数字了。

原理十分简单,对于4 5 1 3 2 这个排列来说,第一位是4,大于(1,2,3)3个数,以这三个数开头的排列共有3*4!个,它们都小于原排列;再看第二位5,在这一位上有4个数小于5,但是由于现在考虑的情况是第二位前都和原排列相同,所以4不能放在这里,因此又有3*3!个排列小于原排列,以此类推一直处理下去,最后得到的答案是从0开始的。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#define LL long long
int a[100];
LL fac[100];
int main(){
    int n;
    fac[0]=1;
    for(int i=1;i<=9;i++) fac[i]=fac[i-1]*i;
    scanf("%d",&n);
    int res=0;
    for(int i=0;i<n;i++) scanf("%d",&a[i]);
    for(int i=0;i<n;i++){
        int k=0;//统计有多少可以排在第i位,且比a[i]小的
        for(int j=i+1;j<n;j++) if(a[j]<a[i]) ++k;
        res+=k*fac[n-1-i];
    }
    printf("是第%d个排列\n",res+1);
}

康托逆展开:

给定排列的序号,求排列。

从第一位开始逐个确定排列的元素。

以5位排列中第66个排列为例:

66-1=65(排列号应从0开始)

用65除 4! = 2,有两个小于第一位的数,因此第一位为3。 65%4!=17

用17除 3! = 2,有两个小于第二位的数,由于第二位前的数已经确定,不能放在第二位,所以3不可能在第二位,第二位为4。

以此类推


代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#define LL long long
bool vis[100];
LL fac[100];
int a[100];
int main(){
    int n;
    fac[0]=1;
    for(int i=1;i<=9;i++) fac[i]=fac[i-1]*i;
    scanf("%d",&n);
    int ord;
    scanf("%d",&ord);
    ord--;
    for(int i=0;i<n;i++){
        int t=ord/fac[n-1-i]+1;//+1转化为有t个数小于等于a[i]
        ord%=fac[n-1-i];
        int k=0;
        int j;
        for(j=1;j<=n;j++){
            if(!vis[j]) k++;
            if(k==t) break;
        }
        a[i]=j; vis[j]=1;
    }

    for(int i=0;i<n;i++) printf("%d ",a[i]);
    printf("\n");
}



你可能感兴趣的:(康托展开)