康托展开
有一个关于排列问题的神奇的公式,康托展开
维基百科是这样描述的:
康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,因此是可逆的。
以下称第x个全排列是都是指由小到大的顺序。
这可以用来求一个排列是全排列中的第几个排列...
关于公式的话,我觉得这个大佬写得非常棒,可以往这边看 ACdreamer
按照我个人的理解是,an表示的是第n个数(从最后一个数字开始从1数)的前面有几个比它小的数字,(n-1)!表示的是(当前的数的位数-1,从1开始数位数)的阶乘。
贴个例题
标题: 排列序数
X星系的某次考古活动发现了史前智能痕迹。
这是一些用来计数的符号,经过分析它的计数规律如下:
(为了表示方便,我们把这些奇怪的符号用a~q代替)
abcdefghijklmnopq 表示0
abcdefghijklmnoqp 表示1
abcdefghijklmnpoq 表示2
abcdefghijklmnpqo 表示3
abcdefghijklmnqop 表示4
abcdefghijklmnqpo 表示5
abcdefghijklmonpq 表示6
abcdefghijklmonqp 表示7
.....
在一处石头上刻的符号是:
bckfqlajhemgiodnp
请你计算出它表示的数字是多少?
这就是计算第几个全排列,如果不知道这个康托展开的话就很难做了...还是要多做题目 才能拓宽知识面...
1 #include<bits/stdc++.h> 2 using namespace std; 3 long long a[17]; 4 5 int main(){ 6 a[0]=1; 7 a[1]=1; 8 for(int i=2; i<17; i++){ 9 a[i]=a[i-1]*i; 10 } 11 long long sum=0; 12 string s="bckfqlajhemgiodnp"; 13 for(int i=0; i<17; i++){ 14 int tmp=0; 15 for(int j=i+1; j<17; j++) 16 if(s[j]<s[i]) tmp++; 17 sum+=a[16-i]*tmp; 18 } 19 cout<<sum<<endl; 20 21 return 0; 22 }
这个还有一个逆运用,就是已知一个数的第几个排列,让你去求这个数是什么...
维基百科是这样描述的:
既然康托展开是一个双射,那么一定可以通过康托展开值求出原排列,即可以求出n的全排列中第x大排列。
如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 #include<bits/stdc++.h> 2 using namespace std; 3 4 int main(){ 5 long long p[16]; 6 p[0]=p[1]=1; 7 for(int i=2; i<16; i++) 8 p[i]=p[i-1]*i; 9 10 int n, m, tmp; 11 cin>>n>>m; 12 tmp=n; 13 vector<int>a; 14 vector<int>v; 15 for(int i=1; i<=n; i++){ 16 a.push_back(i); 17 } 18 m--; 19 int t, r; 20 for(int i=n; i>=1; i--){ 21 t=m/p[i-1]; 22 m=m%p[i-1]; 23 v.push_back(a[t]); 24 a.erase(a.begin()+t); 25 sort(a.begin(), a.end()); 26 } 27 vector<int>::iterator it; 28 for(it=v.begin(); it!=v.end(); it++){ 29 cout<<*it; 30 } 31 cout<<endl; 32 33 return 0; 34 }