思想:数列的每一种排列对应一个唯一的状态,可以用一个固定的数表示这种状态从而实现用简单的数来表示复杂的状态,特别是原状态是一个整体而已知的数据类型无法存储的时候,也能记录该排列在全排列中的位置。
实现思路(自己的理解,不一定对):所谓同一种排列就是对应位置上的数据相同,也就是说位置与位置上的数据构成了一种排列,我们用一种方法将位置与对应的数据结合起来再把整个排列结合起来就能唯一表示这个排列。
康托展开:
an:是指从第n(n从0开始)个数开始这个数在n到最后的数第n个位置上的数排第几位(从小到大排,从0开始)
举个例子说明:
在5个数的排列组合中,计算 34152的康托展开值。
首位是3,则小于3的数有两个,为1和2,,则首位小于3的所有排列组合为
第二位是4,由于第一位小于4,1、2、3中一定会有1个充当第一位,所以排在4之下的只剩2个,所以其实计算的是在第二位之后小于4的个数。因此。
第三位是1,则在其之后小于1的数有0个,所以。
第四位是5,则在其之后小于5的数有1个,为2,所以。
最后一位就不用计算啦,因为在它之后已经没有数了,所以固定为0
根据公式:
所以比34152小的组合有61个,即34152是排第62。
那么知道62能不能推出34152呢?
答案是肯定的!
具体过程如下:
用 61 / 4! = 2余13,说明,说明比首位小的数有2个,所以首位为3。
用 13 / 3! = 2余1,说明,说明在第二位之后小于第二位的数有2个,所以第二位为4。
用 1 / 2! = 0余1,说明,说明在第三位之后没有小于第三位的数,所以第三位为1。
用 1 / 1! = 1余0,说明,说明在第二位之后小于第四位的数有1个,所以第四位为5。
最后一位自然就是剩下的数2。
通过以上分析,所求排列组合为 34152。
这就是康托展开和他的逆展开!
好了,上代码:
void cantor(int s[], LL num, int k){//康托展开,把一个数字num展开成一个数组s,k是数组长度
int t;
bool h[k];//0到k-1,表示是否出现过
memset(h, 0, sizeof(h));
for(int i = 0; i < k; i ++){
t = num / fac[k-i-1];
num = num % fac[k-i-1];
for(int j = 0, pos = 0; ; j ++, pos ++){
if(h[pos]) j --;
if(j == t){
h[pos] = true;
s[i] = pos + 1;
break;
}
}
}
}
void inv_cantor(int s[], LL &num, int k){//康托逆展开,把一个数组s换算成一个数字num
int cnt;
num = 0;
for(int i = 0; i < k; i ++){
cnt = 0;
for(int j = i + 1; j < k; j ++){
if(s[i] > s[j]) cnt ++;//判断几个数小于它
}
num += fac[k-i-1] * cnt;
}
}
附赠经典习题:
康托展开经典题:hdu 1430
http://acm.hdu.edu.cn/showproblem.php?pid=1430