康托(逆)展开(2015.8.6)

康托展开 X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0!。这就是康托展开。康托展开可用代码实现。

——百度百科

这个东西,我们高三的时候称作OhYee第一定理(还有第二第三)。当时做排列组合做到疯的时候推出来的。然而我们数学老师觉得没什么琴里用→_→

对于八数码问题,在储存时有一个问题,就是八数码共有9!=362800种可能,但是如果用9维数组,包含了99=387420489项,显然造成了极大地浪费。

当然,我们可以采用哈希表来进行映射,而哈希函数便可以使用康托展开(OhYee第一定理)。

当年我可是在有OhYee第一定理的情况下做过八数码的,可惜当时竟然没有想到用OhYee第一定理。。。

嗯嗯,我们就不管是康托展开,还是康托逆展开了,反正都是OhYee定理的内容23333333333


含义

对于{1,2,3,4}其排列有:1234,1243,1324,1342,1423,1432,2134,2143,2314,2341,2413,2431,3124,3142,3214,3241,3412,3421,4123,4132,4213,4231,4312,4321共24种。

这样我们就可以按照从小到大的顺序,给他们编上号,就有了一个映射关系

1 2 3 4 5 6 7 8 9 10 11 12
1234 1243 1324 1342 1423 1432 2134 2143 2314 2341 2413 2431
13 14 15 16 17 18 19 20 21 22 23 24
3124 3142 3214 3241 3412 3421 4123 4132 4213 4231 4312 4321

这样将序号和数字的映射就是康托(逆)展开


原理

由上面{1,2,3,4}的例子很容易发现,从小到大排列,有着一定的规律。

以每个数开头的,分别占据了总数(4!)的1/4,即3!。

第一个以1开头的在0×3!+1=1

第一个以2开头的在1×3!+1=7

……

也就是说N个数全排列,第一个数是x的数出现在(x-1)×(N-1)!+1处。

第一个数确定后,我们再看第二个数。假如第一个数是2,那么还有{1,3,4}三个数。

令1=1,2=3,3=4,我们便可看成{1,2,3}三个数的排列

假如是那么第一个2便在1×2!+1=3

再映射回去,也就是第二个是3的在第3个,对照上表看,在第一个是2的数中第二个是3的第一个数就是第3个

就这样,不断类比下去,就可以得到一个数在其排列中的位置。这就是OhYee第一定理

ps:最后的+1只需要加一次


例子

假如我们想要计算在{1,2,3,4}的全排列中,2413的位置

首先有对2,比2小的有1,共1个。1×3!=6

此时剩下1,3,4。

然后对4,比4小的有1,3,共2个。2×2!=4

此时剩下1,3。

然后对1,比1小的有0个。0×1!=0

此时剩下3,比3小的有0个。0×0!=0

所以,2413的位置是6+4+0+0+1=11


逆着来一遍

排列中,第11个是什么呢~

首先划分区间3!=6,也就是6个一组(以相同的数字开头)

这样11-6=5<=6 在第2区间。第一个数是2

再往下划分区间2!=2

5-2=3  3-2=1<=2在第3区间。第二个数是4

再往下划分1!=1

1<=1在第一区间。第三个数是1

再往下划分0!=1

1<=1在第一区间。第四个数是3

所以,第11个数是2413



根据上面的分析,我们需要不断插入删除数字,需要用链表来记录还存在的数

用指针弄一个链表(输出了中间运算过程)

class linked_list{
private:
    struct link{
        int num;
        link *next;
    };
    link *head;

public:
    linked_list(){
        head=NULL;
    }
    void Add(int n){
        link *it;
        if(head==NULL){
            head=new link;
            it=head;
        }else{
            it=head;
            while((it->next)!=NULL){
                it=it->next;
            }
            it->next=new link;
            it=it->next;
        }
        it->num=n;
        it->next=NULL;
        cout<<"add "<<n<<"  ("<<it<<")"<<endl;
    }
    void Del(int n){
        link *it=head;
        if(it->num==n){
            head=it->next;
            delete(it);
            cout<<"delete "<<n<<"   ("<<it<<")\n";
            return;
        }
        while(it->next->num!=n){
            it= it->next;
            if(it->next==NULL)return;
        }
        link *temp=it->next;
        it->next=it->next->next;
        delete(temp);
        cout<<"delete "<<n<<"   ("<<temp<<")\n";
    }
    int get(int n){
        link *it=head;
        int cnt=0;
        cout<<"begin ";
        while(it->num!=n){
            cout<<"---->"<<it<<"("<< it->num <<") ";
            it=it->next;
            cnt++;
        }
        cout<<endl;
        return cnt;
    }
    int get2(int n){
        link *it=head;
        cout<<"begin ";
        while(n>1){
            cout<<"---->"<<it<<"("<< it->num <<") ";
            it=it->next;
            n--;
        }
        cout<<"---->"<<it<<"("<< it->num <<") "<<endl;
        return it->num;
    }
};

其中Add()是向链表最后插入,Del()是删除存储制定数据的节点,get()是获取指定数据前面的节点数,get2()是获取指定节点存储的数

然后是康托(逆)展开

int factorial[]={1,1,2,6,24,120,720,5040,40320};//n!
int KT(int N,int x){
    int ans=1;
    linked_list L;
    stack<int> S;
    while(x){
        S.push(x%10);
        x/=10;
    }
    REP(N){
        L.Add(o+1);
    }
    REP(N){
        int a=L.get(S.top());
        L.Del(S.top());
        S.pop();
        ans+=a*factorial[N-o-1];
    }
    return ans;
}
int KTN(int N,int n){
    int ans=0;
    linked_list L;
    REP(N){
        L.Add(o+1);
    }
    REP(N){
        ans*=10;
        int t=1;
        while(n>factorial[N-o-1]){
            n-=factorial[N-o-1];
            t++;
        }
        int temp=L.get2(t);
        ans+=temp;
        L.Del(temp);
    }
    return ans;
}

最后完整代码

#include <cstdio>
#include <iostream>
#include <stack>
using namespace std;
#define REP(n) for(int o=0;o<n;o++)
int factorial[]={1,1,2,6,24,120,720,5040,40320};//n!

class linked_list{
private:
    struct link{
        int num;
        link *next;
    };
    link *head;//The address of the first link

public:
    linked_list(){
        head=NULL;
    }
    void Add(int n){//Add node
        link *it;
        if(head==NULL){
            head=new link;
            it=head;
        }else{
            it=head;
            while((it->next)!=NULL){
                it=it->next;
            }
            it->next=new link;
            it=it->next;
        }
        it->num=n;
        it->next=NULL;
        cout<<"add "<<n<<"  ("<<it<<")"<<endl;
    }
    void Del(int n){
        link *it=head;
        if(it->num==n){//if the first node is the target
            head=it->next;
            delete(it);
            cout<<"delete "<<n<<"   ("<<it<<")\n";
            return;
        }
        while(it->next->num!=n){
            it= it->next;
            if(it->next==NULL)return;
        }
        link *temp=it->next;
        it->next=it->next->next;
        delete(temp);
        cout<<"delete "<<n<<"   ("<<temp<<")\n";
    }
    int get(int n){
        link *it=head;
        int cnt=0;
        cout<<"begin ";
        while(it->num!=n){
            cout<<"---->"<<it<<"("<< it->num <<") ";
            it=it->next;
            cnt++;
        }
        cout<<endl;
        return cnt;
    }
    int get2(int n){
        link *it=head;
        cout<<"begin ";
        while(n>1){
            cout<<"---->"<<it<<"("<< it->num <<") ";
            it=it->next;
            n--;
        }
        cout<<"---->"<<it<<"("<< it->num <<") "<<endl;
        return it->num;
    }
};

int KT(int N,int x){
    int ans=1;
    linked_list L;
    stack<int> S;
    while(x){
        S.push(x%10);
        x/=10;
    }
    REP(N){
        L.Add(o+1);
    }
    REP(N){
        int a=L.get(S.top());
        L.Del(S.top());
        S.pop();
        ans+=a*factorial[N-o-1];
    }
    return ans;
}
int KTN(int N,int n){
    int ans=0;
    linked_list L;
    REP(N){
        L.Add(o+1);
    }
    REP(N){
        ans*=10;
        int t=1;
        while(n>factorial[N-o-1]){
            n-=factorial[N-o-1];
            t++;
        }
        int temp=L.get2(t);
        ans+=temp;
        L.Del(temp);
    }
    return ans;
}

int main(){
   cout<<KT(4,2413)<<endl;
   cout<<KTN(4,11);
}

点击查看源码

时间: 2024-10-09 18:30:21

康托(逆)展开(2015.8.6)的相关文章

康托逆展开

刚才学弟问,不好意思说不知道康托逆展开是个什么东西= =.然后硬着头皮百度了一下,找到了思路,顺手就敲了一下,发上来吧: 求n个数的第m大的全排列. 思路链接: 戳这里... 代码贴出来 :简单的写下注释..0.0 1 #include <iostream> 2 using namespace std; 3 const int MAXN = 13; 4 int num[MAXN]; 5 int jie[12]={1, 1, 2, 6, 24, 120, 720, 5040, 40320, 36

HDU 1027 Ignatius and the Princess II(康托逆展开)

Ignatius and the Princess II Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 4865    Accepted Submission(s): 2929 Problem Description Now our hero finds the door to the BEelzebub feng5166. He o

康托展开、康托逆展开原理

康托展开 康托展开的公式是 X=an*(n-1)!+an-1*(n-2)!+...+ai*(i-1)!+...+a2*1!+a1*0! 其中,ai为当前未出现的元素中是排在第几个(从0开始). 这个公式可能看着让人头大,最好举个例子来说明一下.例如,有一个数组 s = ["A", "B", "C", "D"],它的一个排列 s1 = ["D", "B", "A", &

康托展开和逆展开

康托展开: 给定一个排列(由n个数排列而成),我们可以计算出该排列在由n个数组成的所有排列中排名第几(按字典序),这就是康托展开. 比如由4个数1,2,3,4组成排列 那么2413在所有的排列中排第几呢? 首先计算第一位数字比2小的排列有多少种,即 1 * fac[3],怎么得来的呢?首先比2小的数字只有1个,那么第一位数字只有一种选择,剩下的三位数字有fac[3]种选择,所以是1 * fac[3] 现在当第一位固定是2,那么来计算第二位数字比4小的排列有多少种呢?首先比4小的数字有1个,那么第

【康拓逆展开】HDU2062Subset sequence

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2062 Problem Description Consider the aggregate An= { 1, 2, -, n }. For example, A1={1}, A3={1,2,3}. A subset sequence is defined as a array of a non-empty subset. Sort all the subset sequece of An in le

hihoCoder #1312 : 搜索三&#183;启发式搜索(A*, 康托展开)

原题网址:http://hihocoder.com/problemset/problem/1312 时间限制:10000ms 单点时限:1000ms 内存限制:256MB   描述 在小Ho的手机上有一款叫做八数码的游戏,小Ho在坐车或者等人的时候经常使用这个游戏来打发时间. 游戏的棋盘被分割成3x3的区域,上面放着标记有1~8八个数字的方形棋子,剩下一个区域为空. 游戏过程中,小Ho只能移动棋子到相邻的空区域上.当小Ho将8个棋子都移动到如下图所示的位置时,游戏就结束了. 小Hi:小Ho,你觉

康托展开-全排列应用

我排第几个 时间限制:1000 ms  |  内存限制:65535 KB 难度:3 描述 现在有"abcdefghijkl"12个字符,将其所有的排列中按字典序排列,给出任意一种排列,说出这个排列在所有的排列中是第几小的? 输入 第一行有一个整数n(0<n<=10000);随后有n行,每行是一个排列: 输出 输出一个整数m,占一行,m表示排列是第几位: 样例输入 3 abcdefghijkl hgebkflacdji gfkedhjblcia 样例输出 1 30271524

hdu 1043 Eight(康托展开,打表)

1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 typedef long long LL; 5 6 /** 7 学习:(1)康托展开的使用 8 (2)路径记录 9 ? A* 10 ? 双向bfs 11 */ 12 13 int t[9],s[9]; 14 bool vis[362880 + 10]; 15 char ans[362880 + 10][42]; 16 int fac[]={1,1

nyoj 139 我排第几个(康托展开)

康托展开 康托展开的公式是 X=an*(n-1)!+an-1*(n-2)!+...+ai*(i-1)!+...+a2*1!+a1*0! 其中,ai为当前未出现的元素中是排在第几个(从0开始). 这个公式可能看着让人头大,最好举个例子来说明一下.例如,有一个数组 s = ["A", "B", "C", "D"],它的一个排列 s1 = ["D", "B", "A", &