康托展开

康托展开是全排列到自然数的双射,可以用于压缩空间。

比如有一个全排列,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 }
康托展开(未AC)

总不能这样水过去,其实就是求出一个排列之后的某个排列,可以说是一种枚举排列的方法。直接给出代码。

 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 }
AC代码
原文地址:https://www.cnblogs.com/Mr94Kevin/p/9764241.html