康托展开
公式
\[ 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;
}
还有逆康托展开什么的就懒的写了,反正就是把公式倒着递推嘛。相信各位大佬都是会的。