目录
Combinatorics(组合数学)
康托展开
由一个排列计算它是全排列中的第几个排列的方法。
公式中x!表示x的阶乘,如果将排列元素写成一行,则a[n]表示从右向左数的第n个数,在其左边没有出现过且比当前数小的数的个数。
如3 5 2 1 4中,比3小的数有2和1,一共2个,则a[5] = 2;到5时,比5小的且没有在它左边出现过的数有1, 2, 4,一共3个,则a[4] = 3……
可以发现对于字典序最小的排列如1 2 3 4 5,使用此公式得到的值是0。这个函数的函数值取值范围是0 ~ (n! – 1)之间的所有整数(包括0和n! - 1)。
利用此公式可以建立一个从排列到连续自然数的双射函数,可用来建立散列表或者将一些全排列相关状态压缩成一个数。
说明:
使用树状数组(Binary Indexed Tree)来获得到当前位置,有多少未出现过的且比它小的数的个数。初始时每个数的个数都是1,遍历过一个数后就把它置0,这样每次只需要查询前缀和就可以获得比当前数小且没遍历到的数的个数。
数据:
permutation[],存放一个全排列。
cantor_pre[],用来维护前缀的树状数组。
fac[],阶乘表,对于一个有n个元素的排列,需要建立0 ~ n – 1的阶乘表。
常量:
const int OFF,偏移量(offset),如果permutation数组中的排列不是从1开始的(比如经常会见到从0开始的全排列),那么就需要将OFF设置成一个数值,使得它加上这个全排列的最小值后等于1(如最小值是0时,OFF需要设置成1)。这是建立树状数组的需要,错误的OFF值可能会导致错误的运算结果或是非法内存访问。
const int LIM,排列元素数量上限。
函数:
lowbit(int x),树状数组结点索引计算需要。
cantor_update(int idx, int diff),树状数组的更新函数。
cantor_query(int idx),树状数组的查询函数。
cantor_init(int n),树状数组的初始化函数,参数n是排列的元素个数。初始化时利用了树状数组第i个结点的值,是原始数据i – lowbit(i) + 1的数据到i的数据之和,因为这里初始化时数据都是1,所以等价于i – (i – lowbit(i)) = lowbit(i)。
cantor_expansion(int n),康托展开的主函数,参数n是排列元素个数,排列应当被存储在permutaion数组中,且从0号下标处开始存储。
inverse_cantor(int n, int x),将第x个排列还原到permutation数组中,是cantor_expansion()函数的逆过程。
#include <iostream>
#include <cstring>
using namespace std;
const int LIM = 10;
const int OFF = 0;
int cantor_pre[LIM];
int permutation[LIM] = {3, 5, 7, 4, 1, 2, 9, 6, 8};
int fac[LIM] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};;
inline int lowbit(int x) {return x & (-x);}
inline void cantor_update(int idx, int diff) {
for (; idx < LIM; idx += lowbit(idx))
cantor_pre[idx] += diff;
}
inline int cantor_query(int idx) {
int ret = 0;
for (; idx > 0; idx -= lowbit(idx))
ret += cantor_pre[idx];
return ret;
}
inline void cantor_init(int n) {
memset(cantor_pre, 0, sizeof cantor_pre);
for (int i = 0; i < n; i++) {
int x = permutation[i] + OFF;
cantor_pre[x] += lowbit(x);
}
}
int cantor_expansion(int n) {
cantor_init(n);
int ret = 0;
for (int i = 1; i <= n; i++) {
ret += cantor_query(permutation[i - 1] + OFF - 1) * fac[n - i];
cantor_update(permutation[i - 1] + OFF, -1);
}
return ret;
}
void inverse_cantor(int n, int x) {
memset(cantor_pre, 0, sizeof cantor_pre);
for (int i = 1; i <= n; i++) {
int tmp = x / fac[n - i], t;
int cur = tmp + 1;
x %= fac[n - i];
while ((t = cantor_query(cur) + tmp + 1) != cur)
cur = t;
permutation[i - 1] = cur - OFF;