先搬一下(戳)维基百科的康托展开(戳):
康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,因此是可逆的。
由于是双射 所以可以求n的全排列里第k大的排列(逆康托展开)
(伪)计算原理: 从某个元素找后面比这个元素小的数的个数,再乘以这个位置每一个数字能有的组合方法数(排列 / 阶乘),得出只考虑从这一位开始到末尾比当前小的排列数,然后加起来就是康托展开求的数(追求难懂的巅峰...........看不懂就看看维基.........
公式:
ai 是整数 且 0 <= ai < i , 1 <= i <= n
ai 的意义:参悟栗子吧
栗子:
例如,3 5 7 4 1 2 9 6 8 展开为 98884。 因为X=2*8!+3*7!+4*6!+2*5!+0*4!+0*3!+2*2!+0*1!+0*0!=98884. 解释: 排列的第一位是3,比3小的数有两个,以这样的数开始的排列有8!个,因此第一项为2*8! 排列的第二位是5,比5小的数有1、2、3、4,由于3已经出现,因此共有3个比5小的数,这样的排列有7!个,因此第二项为3*7! 以此类推,直至0*0!
再贴一下(戳)NOCOW关于康托展开的一页(戳),有代码,看得懂就差不多了
逆康托展开并不是康托展开完全逆过来:
1.减去1,得到比该排列小的排列的数量
2.从高位算起:取摸对应位的阶乘,得到在这一位后面比这一位小的数的个数(所以要注意前面比这一位的数小的要去除)
栗子:
如n=5,x=96时: 首先用96-1得到95,说明x之前有95个排列.(将此数本身减去!) 用95去除4! 得到3余23,说明有3个数比第1位小,所以第一位是4. 用23去除3! 得到3余5,说明有3个数比第2位小,所以是4,但是4已出现 过,因此是5. 用5去除2!得到2余1,类似地,这一位是3. 用1去除1!得到1余0,这一位是2. 最后一位只能是1. 所以这个数是45321.
再贴上忘了什么时候写的代码:
1 typedef long long ll; 2 const ll factorial[11] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800}; 3 int num[101]; 4 ll Cantorex(int n){ 5 for(int i = 0; i < n; ++i) cin >> num[i]; 6 ll sum = 0; 7 for(int i = 0; i < n; ++i){ 8 int t = 0; 9 for(int j = i + 1; j < n; ++j) if(num[j] < num[i]) ++t; 10 sum += t * factorial[n-i-1]; 11 } 12 return sum; 13 } 14 ll uCantorex(ll sum, int n){ 15 memset(num, 0, sizeof(num)); 16 sum -= 1; 17 int x = 0; 18 ll ans = 0; 19 for(int i = n - 1; i > 0; --i){ 20 ll k = sum / factorial[i] + 1; 21 sum %= factorial[i]; 22 while(num[k]) ++k; 23 num[k] = 1; 24 ans = ans * 10 + k; 25 } 26 for(int i = 1; i <= n; ++i) 27 if(!num[i]) ans = ans * 10 + i; 28 return ans; 29 }
Done!
时间: 2024-10-10 04:32:03