康拓展開及其逆運算和全排列函式
有所摘抄,但重要的是自己的想法。
康託展開是一個全排列到一個自然數的雙射,常用於構建雜湊表時的空間壓縮。 康託展開的實質是計算當前排列在所有由小到大全排列中的順序,因此是可逆的。
X = a[n]*(n-1)! + a[n-1]*(n-2)! + ... + a[i]*(i-1)! + ... + a[1]*0!
index: 1 2 ... n-i+1 ... n
其中,a[i]為整數,並且0<=a[i]<i,1<=i<=n
a[i] 是:假設 一串數字中第index個元素為e, a[i]表示第index個數e之後的數比e小的數的個數
舉個例子:
原序列: 1 2 3 4 5 6 7 8
求: 3 5 7 4 1 2 9 6 8
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!
用途:
顯然,n位(0~n-1)全排列後,其康託展開唯一且最大約為n!,因此可以由更小的空間來儲存這些排列。由公式可將X逆推出對應的全排列。
總之就是用來壓縮空間的。。本人也不大懂。
例題:
現在有"abcdefghijkl”12個字元,將其所有的排列中按字典序排列,給出任意一種排列,說出這個排列在所有的排列中是第幾小的?
每行輸入一行字串,保證是a~l這12個字元的某種排列
EOF結束
輸出一個整數,代表這個排列排在第幾位
abcdefghijkl
abcdefghiklj
gfkedhjblcia
1
4
260726926
#include <iostream> #include <cstdio> #define LL long long using namespace std; int factorial(int l){ if(l == 0 || l == 1) return 1; else return l * factorial(l-1); } int main(){ char a[13]; LL ans; int i, j, k; while(~scanf("%s", a+1)){ ans = 1; for(i = 1; i <= 12; ++i){ k = 0; for(j = i+1; j <= 12; ++j) if(a[j] < a[i]) ++k; ans += factorial(12-i) * k; } printf("%lld\n", ans); } return 0; }
另外,康託展開的逆運算,即通過知道它是第幾個全排列求全排列
例1 {1,2,3,4,5}的全排列,並且已經從小到大排序完畢 (1)找出第96個數 首先用96-1得到95 用95去除4! 得到3餘23 有3個數比它小的數是4 所以第一位是4 用23去除3! 得到3餘5 有3個數比它小的數是4但4已經在之前出現過了所以第二位是5(4在之前出現過,所以實際比5小的數是3個) 用5去除2!得到2餘1 有2個數比它小的數是3,第三位是3 用1去除1!得到1餘0 有1個數比它小的數是2,第二位是2 最後一個數只能是1 所以這個數是45321 (2)找出第16個數 首先用16-1得到15 用15去除4!得到0餘15 用15去除3!得到2餘3 用3去除2!得到1餘1 用1去除1!得到1餘0 有0個數比它小的數是1 有2個數比它小的數是3 但由於1已經在之前出現過了所以是4(因為1在之前出現過了所以實際比4小的數是2) 有1個數比它小的數是2 但由於1已經在之前出現過了所以是3(因為1在之前出現過了所以實際比3小的數是1) 有1個數比它小得數是2 但由於1,3,4已經在之前出現過了所以是5(因為1,3,4在之前出現過了所以實際比5小的數是1) 最後一個數只能是2 所以這個數是14352 最後,還有一個全排列函式 :next_permutation函式 這是一個求一個排序的下一個排列的函式,可以遍歷全排列,要包含標頭檔案<algorithm>與之完全相反的函式還有prev_permutation
但做本題是會超時(不用想也是,一個一個求肯定TLE) 該函式可以對很多型別的陣列序列進行求下一個字典序排列,即按全排的順序求下一個排序序列 這裡也貼一下本題的TLE程式碼(嘿嘿)
//TLE
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int main(){
char b[20];
int res;
while(~scanf("%s", b)){
char a[] = "abcdefghijkl";
char aa[] = "gfkedhjblcia"
if(strcmp(a, b) == 0) {printf("1\n"); continue;}
res = 1;
while(next_permutation(a, a+12)){
++res;
if(strcmp(a, b) == 0) {
printf("%d\n", res);
break;
}
}
}
return 0;
}
此函式各種型別的用法詳解:http://blog.sina.com.cn/s/blog_9f7ea4390101101u.html
2017/4/1日增: 1.//康拓程式碼實現
#include <bits/stdc++.h>
using namespace std;
int factor(int k){
if(k == 0 || k == 1) return 1;
return k * factor(k-1);
}
int main(){
int n, i, ans, k;
char jk[25];
while(cin >> jk+1){
ans = 0, n = strlen(jk+1);
for(i = 1; i <= n; ++i){
k = 0;
for(int j = i+1; j <= n; ++j)
if(jk[i] > jk[j]) ++k;
ans += k * factor(n-i);
}
cout << ans+1 << endl; //最小序列ans為0,同理ans+1;
}
return 0;
}
2.//逆康拓程式碼實現
#include <bits/stdc++.h>
using namespace std;
int factor(int k){
if(k == 0 || k == 1) return 1;
return k * factor(k-1);
}
int ans[105], book[105];
int main(){
int n, k, m, key, x, j;
while(cin >> n >> m){
memset(book, 0, sizeof book);
--n; //最小序列ans為0,所以需要--;
for(int i = 1; i <= m; ++i){
k = factor(m-i);
key = n/k+1; //在(m-i)!的係數+1,即當前位置的數值大於後面的個數+1,+1為了方便後面求該數
n %= k; //更新n
j = 1; //初始化j
while(key){
if(!book[j]) --key;
++j;
}
ans[i] = j-1; //當找到該數時j多加了一次1
book[j-1] = 1;
}
for(int i = 1; i <= m; ++i)
printf("%d", ans[i]);
printf("\n");
}
return 0;
}