康托展开和逆康托展开
前言
康托展开和逆康托展开是针对全排列问题的。
全排列:对于一个长度为 $N$ 的数组 $a$ , 满足 $1≤ai≤N$ 并且 各个元素互不相同。
数组 | 顺序 |
---|---|
1 2 3 4 5 | 1 |
1 2 3 5 4 | 2 |
1 2 5 3 4 | 3 |
原理
康托展开的例子
如果给一个$N=5$ 的数组$[3,2,5,4,1]$
对于第一个数据3 , 后面比它小的数据有 2 个,所以 以 [1] 、[2] 为开头的全排列,都在它前面,$2*4!$
对于第二个数据2 , 后面比它小的数据有 1 个,所以 以 [3,1] 为开头的全排列,都在它前面,$1*3!$
对于第三个数据5,后面比它小的数据有 2 个,所以 以 [3,2,1]、[3,2,4] 开头的全排列,都在它前面,$2*2!$
对于第四个数据4,后面比它小的数据有 1 个,所以 以 [3,2,5,1] 开头的全排列,都在它前面,$1*1!$
对于第五个数据1,后面比它小的数据有 0 个,所以没有全排列在它前面,$0$
$$
2*4!+1*3!+2*2!+1*1!+0*0!+1
$$
因为本身的顺序也算在其中,所以额外加1
代码
使用数状数组来进行统计 $ai$ 后面有多少比它小的元素。
#include
#include
#include
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int MAXN = 1e6+5;
ll fac[MAXN];
ll node[MAXN];
inline void Add(int i,int N){
while(i<=N){
node[i]-=1;
i+=i&-i;
}
}
inline ll Sum(int i){
int s=0;
while(i){
s+=node[i];
i-=i&(-i);
}
return s;
}
inline void Init(int N){
ios::sync_with_stdio(false);
cin.tie(0);
fac[0]=1;
memset(node,0,sizeof(node));
for(ll i=1;i<=1e6;++i){
fac[i]=(fac[i-1]*i)%mod;
node[i]=i&-i;
}
}
int main(){
int N,x;
ll sum=1;
cin>>N;
Init(N);
for(int i=0;i>x;
Add(x,N);
sum+=Sum(x)*fac[N-i-1];
sum%=mod;
}
cout<