康托展开
康托展开(cantor expansion)
康托展开可以求出一个排列在全排列中的排名(\(0 \sim n!-1\))。
对于排列中的某一个元素 \(A_i\) ,假设它后面有 \(k\) 个元素小于 \(A_i\) ,那么我们可以将任意一个元素放到 \(A_i\) 的位置,然后剩下的元素随意摆放得到更小的排列,也就是说,排名 \(X\) 对于排列 \(A\) 有:
\(X = a_i(n-1)! + a_2(n-2)! + ... + a_{n-1}(n-(n-1))! + a_n(n-n)!\)
其中 \(a_i\) 表示在 \(i\) 位置后面,且元素值小于 \(A_i\) 的元素个数。
Code
LL res = 0;
for (int i = 1; i <= n; i ++ )
{
int cnt = 0;
// 找出后面有几个小于A[i]的元素
for (int j = i + 1; j <= n; j ++ )
if (A[j] < A[i]) cnt ++ ;
res += fact[n - i] * cnt; // fact[i] == i!
}
逆康托展开
逆康托展开可以根据给定的排名,求出对应的序列。
逆着康托展开即可,如 \(X \ / \ (n-1)!\) 即可求出 \(a_i\) ,我们只需要找出从小到大(\(1 \sim n\)) 中没有用过的排第 \(a_i + 1\) 位的数字即可(因为后面要有 \(a_i\) 个元素小于它)。
Code
memset(st, 0, sizeof st);
for (int i = 1; i <= n; i ++ )
{
LL num = res / fact[n - i];
res %= fact[n - i];
for (int j = 1; j <= n; j ++ )
{
// 只有没有用过的数字才需要考虑
if (!st[j])
{
if (!num) { A[i] = j; st[j] = true; break; }
else -- num ;
}
}
}