康托展开

康托展开

公式

\[ ans=1+\sum_{i=1}^{n} A[i]\times(n-i)! \]

原理

如我想知道321是{1,2,3}中第几个小的数可以这样考虑 :

第一位是3,当第一位的数小于3时,那排列数小于321 如 123、 213 ,小于3的数有1、2 。所以有2× 2!个。再看小于第二位2的:小于2的数只有一个就是1 ,所以有1× 1!=1 所以小于321的{1,2,3}排列数有2×2!+1×1!=5个。所以321是第6个小的数。 2 ×2!+1× 1!+0× 0!就是康托展开。

再举个例子:1324是{1,2,3,4}排列数中第几个大的数:第一位是1小于1的数没有,是0个 0× 3! 第二位是3小于3的数有1和2,但1已经在第一位了,所以只有一个数2,1×2! 。第三位是2小于2的数是1,但1在第一位,所以有0个数 0×1! ,所以比1324小的排列有0×3!+1× 2!+0×1 !=2个,1324是第三个小数。(摘自百度)

用处

洛谷模板题是用于求某排列的排名。-->
传送门

此题要求:给出一个排列求他在全排列中的排名。 直接套式子。

打暴力的复杂度是 $ o(n^2) $,水不过去。

于是我们想到了用树状数组,具体写法还是看我以前写的一篇博客-->传送门

先预处理阶乘,用数组存,o(n),然后用类似于权值线段树的树状数组来节省时间来判重。

上代码

#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
template <typename T>
inline void read(T &x) {
    x = 0;
    int f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + (ch ^ 48);
        ch = getchar();
    }
    x *= f;
    return;
}
template <typename T>
inline void write(T x){
    if(x < 0) {
        putchar('-');
        x = -x;
    }
    if(x > 9)
        write(x/10);
    putchar(x % 10 + '0');
    return;
}
//-------------------------以下是正文
int q[1000005],n;
long long ans,fac[1000005];
inline int lowbit(int x){return x&(-x);}
inline void update(int x,int y){
    for(int i=x;i<=n;i+=lowbit(i))
        q[i] += y;
}
inline int getsum(int a)
{
    int ans=0;
    for(int i=a;i;i-=lowbit(i))
        ans+=q[i];
    return ans;
}
int main(){
    int x;
    read(n);
    fac[0]=1;
    for(int i=1;i<=n;i++)
    {
        fac[i]=fac[i-1]*i%mod;
        update(i,1);//一开始初始化
    }
    for(int i=1;i<=n;i++)
    {
        read(x);
        ans=(ans+(getsum(x)-1)%mod*fac[n-i])%mod;
        update(x,-1);//用一个×一个
    }
    write(ans+1);
    cout<<endl;
}

还有逆康托展开什么的就懒的写了,反正就是把公式倒着递推嘛。相信各位大佬都是会的。

上一篇:321 拼接最大数


下一篇:C#中"?"(问号)相关语法糖