康托展开:
X=an(n-1)!+an-1(n-2)!+…+ai(i-1)!+…+a21!+a1*0!
ai为整数,并且0<=ai<i(1<=i<=n)
应用实例:
{1,2,3,4,…,n}的排列总共有n!种,将它们从小到大排序,怎样知道其中一种排列是有序序列中的第几个?
如 {1,2,3} 按从小到大排列一共6个:123 132 213 231 312 321。想知道321是{1,2,3}中第几个大的数。
这样考虑:第一位是3,小于3的数有1、2 。所以有22!个。再看小于第二位,小于2的数只有一个就是1 ,所以有11!=1 所以小于32
的{1,2,3}排列数有22!+11!=5个。所以321是第6个大的数。22!+11!是康托展开。
再举个例子:1324是{1,2,3,4}排列数中第几个大的数:第一位是1小于1的数没有,是0个,03!,第二位是3小于3的数有1和2,但1已经在第一位了,所以只有一个数2,12! 。第三位是2小于2的数是1,但1在第一位,所以有0个数,01!,所以比1324小的排列有03!+12!+01!=2个,1324是第三个大数。
先做一下预处理
void init() {
fact[0] = 1;
for(int i = 1;i <= 9; ++i) fact[i] = fact[i - 1] * i;
// 递推求阶乘
}
// 或者直接打表
int fact[10] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
主要代码
int cantor( ) {
int res = 0;
for(int i = 0;i < n; ++i) {
int cnt = 0;
for(int j = i + 1;j < n; ++j) if(s[j] <s[i]) cnt ++;
// 找到s[i]是当前数列中未出现的数中第几小的
// 从1开始,即1-n的全排列
// 从0开始,就变成了0-n的全排列
res += cnt * fact[n - i - 1];
}
return res + 1;//返回排名
return res// 返回哈希表
}
托展开的逆运算:
{1,2,3,4,5}的全排列已经从小到大排序,要找出第16个数:
-
首先用16-1得到15
-
用15去除4! 得到0余15
-
用15去除3! 得到2余3
-
用3去除2! 得到1余1
-
用1去除1! 得到1余0
有0个数比它小的数是1
所以第一位是1
有2个数比它小的数是3,但1已经在之前出现过了所以是4
有1个数比它小的数是2,但1已经在之前出现过了所以是3
有1个数比它小的数是2,但1,3,4都出现过了所以是5
最后一个数只能是2
所以这个数是14352
/* 康托展开的逆运算.
{1...n}的全排列,中的第k个数为s[] */
void invKT(int n, int k, int s[])
{
int i, j, t, vst[8]={0};
k--;
for (i=0; i<n; i++)
{
t = k/fact[n-i-1];
for (j=1; j<=n; j++)
if (!vst[j])
{
if (t == 0) break;
t--;
}
s[i] = j;
vst[j] = 1;
k %= fact[n-i-1];
}
}