康托展开

康托展开(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 ;
        }
    }
}
posted @ 2021-11-20 13:54  Horb7  阅读(40)  评论(0编辑  收藏  举报