康拓展开
定义:
康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大全排列中的名次,因此是可逆的。
把一个整数X展开成如下形式:
X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[2]*1!+a[1]*0!
X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[2]*1!+a[1]*0!
其中,a为整数,并且0<=a[i]<i(1<=i<=n)。
所以比34152小的组合有61个,即34152是排第62。
康托展开的最基本应用应该就是,求一个排列(按字典数)的序号(就是第几个)。
而其逆运算就是求序号对应的排列。
康托展开和逆康托展开
康托展开举例
再举个例子说明。
在
5个数的排列组合中,计算 34152的康托展开值。
在
![](https://gss0.bdstatic.com/94o3dSag_xI4khGkpoWK1HF6hhy/baike/s%3D79/sign=d9e56bf2d154564ee165e630b2de073a/b219ebc4b74543a91297ad2d12178a82b8011446.jpg)
首位是3,则小于3的数有两个,为1和2,
,则首位小于3的所有排列组合为
![](https://gss1.bdstatic.com/-vo3dSag_xI4khGkpoWK1HF6hhy/baike/s%3D57/sign=82f2c35a8bd6277fed12323f2a38b4fa/d1a20cf431adcbef73fa29e7a0af2edda2cc9f00.jpg)
![](https://gss3.bdstatic.com/-Po3dSag_xI4khGkpoWK1HF6hhy/baike/s%3D94/sign=8ee235151d38534388cf8b259213c847/94cad1c8a786c917c4f69440c43d70cf3ac757d6.jpg)
第二位是4,由于第一位小于4,1、2、3中一定会有1个充当第一位,所以排在4之下的只剩2个,所以其实计算的是在第二位之后小于4的个数。因此
。
![](https://gss0.bdstatic.com/94o3dSag_xI4khGkpoWK1HF6hhy/baike/s%3D57/sign=a5d1832f9525bc312f5d019f5fdf30a9/aa64034f78f0f73627e71ae00655b319ebc41331.jpg)
第三位是1,则在其之后小于1的数有0个,所以
。
![](https://gss1.bdstatic.com/-vo3dSag_xI4khGkpoWK1HF6hhy/baike/s%3D57/sign=77db5956e550352ab561250f5243cbad/d0c8a786c9177f3e2a21aaec7ccf3bc79f3d563d.jpg)
第四位是5,则在其之后小于5的数有1个,为2,所以
。
![](https://gss2.bdstatic.com/-fo3dSag_xI4khGkpoWK1HF6hhy/baike/s%3D55/sign=882208b2fa246b607f0eb271eaf8e2ac/dc54564e9258d10985f5ff53dd58ccbf6c814d38.jpg)
最后一位就不用计算啦,因为在它之后已经没有数了,所以
固定为0
![](https://gss0.bdstatic.com/94o3dSag_xI4khGkpoWK1HF6hhy/baike/s%3D29/sign=4327609e74f0f736dcfe4b080b555348/359b033b5bb5c9eafd8d5502d939b6003bf3b3d1.jpg)
根据公式:
![](https://gss0.bdstatic.com/94o3dSag_xI4khGkpoWK1HF6hhy/baike/s%3D322/sign=f917ca3d8613632711edc431a38da056/359b033b5bb5c9eafd065502d939b6003bf3b35e.jpg)
所以比34152小的组合有61个,即34152是排第62。
逆康托展开举例
一开始已经提过了,康托展开是一个全排列到一个自然数的双射,因此是可逆的。即对于上述例子,在
给出61可以算出起排列组合为34152。由上述的计算过程可以容易的逆推回来,具体过程如下:
![](https://gss0.bdstatic.com/94o3dSag_xI4khGkpoWK1HF6hhy/baike/s%3D79/sign=d9e56bf2d154564ee165e630b2de073a/b219ebc4b74543a91297ad2d12178a82b8011446.jpg)
用 61 / 4! = 2余13,说明
,说明比首位小的数有2个,所以首位为3。
![](https://gss0.bdstatic.com/-4o3dSag_xI4khGkpoWK1HF6hhy/baike/s%3D57/sign=96f325c761224f4a5399731408f7918d/8d5494eef01f3a2909fd862f9525bc315c607c1d.jpg)
用 13 / 3! = 2余1,说明
,说明在第二位之后小于第二位的数有2个,所以第二位为4。
![](https://gss2.bdstatic.com/9fo3dSag_xI4khGkpoWK1HF6hhy/baike/s%3D57/sign=d686517952df8db1b82e7c630b230cef/aec379310a55b3196122d2944fa98226cefc1773.jpg)
用 1 / 2! = 0余1,说明
,说明在第三位之后没有小于第三位的数,所以第三位为1。
![](https://gss3.bdstatic.com/-Po3dSag_xI4khGkpoWK1HF6hhy/baike/s%3D57/sign=d4d702d8c53d70cf48faaa0afadc6bec/29381f30e924b89902e829d962061d950b7bf67f.jpg)
用 1 / 1! = 1余0,说明
,说明在第二位之后小于第四位的数有1个,所以第四位为5。
![](https://gss2.bdstatic.com/-fo3dSag_xI4khGkpoWK1HF6hhy/baike/s%3D55/sign=6afce245a8c27d1ea1263bc119d578c4/63d0f703918fa0ecb63934ec2a9759ee3c6ddb51.jpg)
最后一位自然就是剩下的数2。
通过以上分析,所求排列组合为 34152。
例题
P2272【康托展开】数字排列
问题描述
{1,2,3} 三个数的全排列可看做6个数字,按从小到大排序得到序列a:123,132,213,231,312,321
下面有两种提问:
1.数字231在序列a中排名第几?回答4
2.数列a中排名第5的数字是多少?回答312
给出n个数字(1,2,3,...,n),回答关于序列a的1和2两种提问。
输入格式
第一行,两个整数n和m,n表示数字1到n(n<=9)构成的全排列,m(m<=100,000)表示询问数
接下来m行,表示询问,每行两个整数x和y,x=1表示第一种询问,回答y的排名;x=2表示第2种询问,答出排名为y的数字
输出格式
m行,每行一个整数,表示对应的答案。
样例输入
3 2
1 231
2 5
样例输出
4
312
提示
结果巨大,建议用long long 类型
时间限制 : 20000 MS 空间限制 : 65536 KB |
代码
1 #include <stdio.h> 2 #include <bits/stdc++.h> 3 using namespace std; 4 char ch; 5 bool flag[15]; 6 long long n, m, x, num; 7 long long a[15], b[15]; 8 int q[10]= {1,1,2,6,24,120,720,5040,40320,362880}; 9 inline int read() 10 { 11 int s=0; 12 char c=getchar(); 13 while (c<'0' || c>'9') c=getchar(); 14 while (c>='0' && c<='9') s=s*10+c-'0',c=getchar(); 15 return s; 16 } 17 int kangtuo() 18 { 19 long long i, j, num = 0; 20 num = (a[1] - 1) * q[n - 1]; 21 for(i = 2; i <= n; i ++) 22 { 23 long long temp = 0; 24 for(j = 1; j < i; j ++) 25 if(a[j] < a[i])temp ++; 26 num += (a[i] - temp -1) * q[n - i]; 27 } 28 return num + 1; 29 } 30 void nikangtuo(int num) 31 { 32 long long i, j, t; 33 num --; 34 for(i = 1; i <= n; i ++) 35 { 36 t = num / q[n - i]; 37 for(j = 1; j <= n; j ++) 38 if(!flag[j]) 39 { 40 if(!t)break; 41 t --; 42 } 43 flag[j] = true; 44 num %= q[n - i]; 45 b[i] = j; 46 } 47 } 48 int main() 49 { 50 n=read(),m=read(); 51 while(m --) 52 { 53 x=read(); 54 if(x == 1) 55 { 56 memset(flag, 0, sizeof(flag)); 57 for(int i = 1; i <= n; i ++) 58 { 59 cin>>ch; 60 a[i] = ch - '0'; 61 } 62 cout<<kangtuo()<<endl; 63 } 64 else 65 { 66 memset(b, 0, sizeof(b)); 67 memset(flag, 0, sizeof(flag)); 68 num=read(); 69 nikangtuo(num); 70 for(int i = 1; i <= n; i ++)cout<<b[i]; 71 cout<<endl; 72 } 73 } 74 }