康托展开
2022/10
很早就知道这个东西了,不过最近才用上(指教练要求默写)索性来写一下。
康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,因此是可逆的。
主要是用于全排列的一种方法,可以在很小的常数复杂度内求出一个排列的排行(第几个)。
其逆定理就是给出一个排列的排行,获得这个排列,下面主要通过这两个方面介绍康托展开。
对于求排名,给出如下公式:
\[X=\sum_{i=1}^{n}rank_i \times (n-i)!
\]
其中 \(rank_i\) 的意思为 \(a_i\) 后比 \(a_i\) 小的数的个数。
我们直接模拟出阶乘,并可以在数据大的时候用树状数组维护 \(rank\)。
初始化阶乘:
void init(int x)
{
fact[0] = 1;
for(int i = 1; i <= x; i++) fact[i] = fact[i - 1] * i;
}
模拟公式:
int cantor(int a[],int n)
{
int res = 0;
for(int i = 0; i < n; i++)
{
int cnt = 0;
for(int j = i + 1; j < n; j++) if(a[j] < a[i]) cnt++;
res += cnt * fact[n - i - 1];
}
return res + 1;
}
接下来就是逆康托展开,我们以 107 作为例子介绍(其中 \(n=5\) )
首先,由于是第 107 个排列,所以它前面有 107-1=106 个排列
106 / (5-1)! = 4,余数为 10,因此在第一个数后面有四个比第一个数小的数,所以第一个数为 5
10 / (5-2)! = 1,余数为 4,因此在第二个数后面有一个比第二个数小的数,所以第一个数为 2
4 / (5-3)! = 2,余数为 0,因此在第三个数后面有二个比第三个数小的数,所以第一个数为 4
0 / (5-4)! = 0,余数为 0,因此在第四个数后面有零个比第四个数小的数,所以第一个数为 1
0 / (5-5)! = 0,余数为 0,因此在第五个数后面有零个比第五个数小的数,所以第一个数为 3
综上,排行为 107 的排列为 52413。
通过这个例子,大致可以理解如何通过排名还原排列(自证)
根据以上方法模拟出来的逆康托展开:
vector<int> incantor(int x,int n)
{
x--;
vector<int> res(n);
int cnt;
bool flag[20];
memset(flag, 0, sizeof flag);
for(int i = 0; i < n; i++)
{
cnt = x/fact[n - i + 1];
x %= fact[n - i + 1];
for(int j = 1; j <= n; j++)
{
if(flag[j]) continue;
if(!cnt)
{
flag[j] = 1;
res[i] = j;
break;
}
cnt--;
}
}
return res;
}

浙公网安备 33010602011771号