康托展开是全排列到自然数的双射,可以用于压缩空间。
比如有一个全排列,p1p2...pn,那么其康托展开X=a1(n-1)!+a2(n-2)!+...+an0!。其中a1等是指当前未在排列中出现过且小于当前数字的数字个数。这样我们就把一个全排列映射成了一个自然数。
上述X就表示在所有全排列中,小于该排列的有多少个。其实也就意味着我们可以通过X求出原排列。
求出X/(n-1)!,就可以知道第一位上的数字,然后令X=X%(n-1)!,不断重复上述过程,直到X=0为止,我们就求出了前n-1位上的数字,而最后一位自然也就确定了。
1 //需要预处理出n!等 2 3 inline int cantor() { //求某个排列的次序x 4 int x = 0; 5 for (int i = 1; i <= n; ++i) { 6 int small = 0; 7 for (int j = i + 1; j <= n; ++j) 8 if (a[j] < a[i]) ++small; 9 x += small * fact[n - i]; 10 } 11 return x + 1; 12 } 13 14 int vis[maxn]; 15 16 inline void decantor(int x) { //求第x大排列 17 memset(vis, 0, sizeof(vis)); 18 --x; 19 for (int i = 1, j; i <= n; ++i) { 20 int t = x / fact[n - i]; 21 for (j = 1; j <= n; ++j) 22 if (!vis[j]) { 23 if (!t) break; 24 --t; 25 } 26 printf("%d ", j); 27 vis[j] = 1; 28 x %= fact[n - i]; 29 } 30 return; 31 }
NOIP2004普及组有道题:火星人(在洛谷上的链接:https://www.luogu.org/problemnew/show/P1088),当时第一反应就是康托展开,但比较懒,写了个STL就水过去了,后来尝试用康托展开,发现并不行,因为这道题N很大,但M较小,假若N较小,M很大,我想康托展开还是很实用的。但这里也给出代码。
1 #include <cstdio> 2 #include <cstring> 3 4 const int maxn = 1e4 + 5; 5 6 int n, m, a[maxn]; 7 long long fact[maxn]; 8 9 inline int cantor() { //求某个排列的次序x 10 int x = 0; 11 for (int i = 1; i <= n; ++i) { 12 int small = 0; 13 for (int j = i + 1; j <= n; ++j) 14 if (a[j] < a[i]) ++small; 15 x += small * fact[n - i]; 16 } 17 return x + 1; 18 } 19 20 int vis[maxn]; 21 22 inline void decantor(int x) { //求第x大排列 23 memset(vis, 0, sizeof(vis)); 24 --x; 25 for (int i = 1, j; i <= n; ++i) { 26 int t = x / fact[n - i]; 27 for (j = 1; j <= n; ++j) 28 if (!vis[j]) { 29 if (!t) break; 30 --t; 31 } 32 printf("%d ", j); 33 vis[j] = 1; 34 x %= fact[n - i]; 35 } 36 return; 37 } 38 39 int main() { 40 scanf("%d%d", &n, &m); 41 fact[0] = 1; 42 for (int i = 1; i <= n; ++i) fact[i] = fact[i - 1] * i; 43 for (int i = 1; i <= n; ++i) scanf("%d", &a[i]); 44 decantor(cantor() + m); 45 return 0; 46 }
总不能这样水过去,其实就是求出一个排列之后的某个排列,可以说是一种枚举排列的方法。直接给出代码。
1 #include <cstdio> 2 #include <algorithm> 3 4 using namespace std; 5 6 const int maxn = 1e4 + 5; 7 8 int n, m, a[maxn]; 9 10 inline void add() { 11 for (int i = n - 1; i >= 1; --i) { 12 if (a[i] > a[i + 1]) continue; 13 for (int j = n; j >= 1; --j) { 14 if (a[j] < a[i]) continue; 15 swap(a[j], a[i]); 16 sort(a + i + 1, a + n + 1); 17 return; 18 } 19 } 20 } 21 22 int main() { 23 scanf("%d%d", &n, &m); 24 for (int i = 1; i <= n; ++i) scanf("%d", &a[i]); 25 for (int i = 1; i <= m; ++i) add(); 26 for (int i = 1; i <= n; ++i) printf("%d ", a[i]); 27 return 0; 28 }