杂项之 - 康托展开

在洛谷上闲逛时无意中看到了这个东东,顺便学了一下

Part1 康托展开是什么

康拓展开是一种将排列映射为一个自然数的双射

康托展开可以用来求一个 \(1\sim n\) 的任意排列的排名。

Part2 康托展开的公式

对于一个排列 \(a_1 \dots a_n\)
\(1\sim n\) 的所有排列按字典序排序,这个排列的位次就是它的排名。

他的排名为 $$ \sum_{i=1}^{n} b_{i}*(n-i)+1 $$

其中 \(b_{i}\) 表示在所以还未出现过的数之中有多少个比这个数小

证明如下

设有排列 \(p=a_1,a_2 \dots a_n\) ,那么对任意字典序比 \(p\) 小的排列,一定存在 \(i\) ,使得其前 \(i-1 (1 \le i <n )\) 位与 \(p\) 对应位相同,第 \(i\) 位比 \(p_i\) 小,后续位随意。于是对于任意 \(i\) ,满足条件的排列数就是从后 \(n-i+1\) 位中选一个比 \(a_i\) 小的数、并将剩下 \(n-i\) 个数任意排列的方案数,即为 \(b_i*(n-1)!\) .遍历 \(i\) 即得总方案数 为 $ \sum_{i=1}^{n} b_{i}*(n-i) $ 再加1即为排名。

实现

阶乘预处理一下即可
\(b_{i}\) 若使用暴力 总的复杂度是 \(O(n^2)\)
若使用树状数组 总的复杂度是 \(O(n \log n)\)

实现代码如下

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define lowbit(x) (-x) & x
const ll mod_ = 998244353;
ll bit[1000010];
int n;
void change(int x, ll delt)
{
    while (x <= n)
    {
        bit[x] += delt;
        x += lowbit(x);
    }
}
ll query(int x)
{
    ll insid = 0;
    while (x)
    {
        insid += bit[x];
        x -= lowbit(x);
    }
    return insid % mod_;
}
ll jie[1100001];
int a[1014514];
void init()//预处理阶乘
{
    jie[0] = 1;
    for (ll oo = 1; oo <= (ll)n; oo++)
    {
        jie[oo] = (jie[oo - 1] * oo) % mod_;
        jie[oo] %= mod_;
    }
}
ll conto()//康托展开
{
    for (int yy = 1; yy <= n; yy++)//加入所有数
    {
        change(a[yy], 1);
    }
    ll insid = 0;
    for (int ww = 1; ww <= n; ww++)
    {
        insid += (query(a[ww] - 1) * jie[n - ww] % mod_) % mod_;//计算排名
        change(a[ww], -1);//将这个数从未出现过的数中删去
        insid %= mod_;
    }
    insid++;
    return insid % mod_;
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> n;
    init();
    for (int ee = 1; ee <= n; ee++)
    {
        cin >> a[ee];
    }
    cout << conto();
    return 0;
}
posted @ 2024-10-09 21:29  sea-and-sky  阅读(14)  评论(0编辑  收藏  举报