题解:P5367 【模板】康托展开
ICU_midway_azuma
·
·
题解
康托展开,即求一个排列在其全排列中,按字典序排序后所处的位置。
如,1\sim 3 的全排列中:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
1 3 2 排在第 2 位。
考虑逐位比对的方法:
如对于排列 2 3 1,分析过程如下:
第 1 位是 2,比其更小的数有 1(所有首位小于 2 的排列的字典序一定比 2 3 1 小),该位结果为 1\times 2!=2。
第 2 位是 3,比其更小的数有 1 和 2,但 2 我们已经用过了(上一位比 2 更小的已经在上一步被统计过,比 2 更大的不被统计,故这里只统计上一位为 2 的情况),所以比其更小的数有 1 个,该位结果为 1\times 1!=1。
最后一位以此类推,没有比 1 更小的数,该位结果为 0\times 0!=0。
比 2 3 1 字典序小的排列有 2+1+0=3 个,排在第 4 位。
综上,我们总结:
设排列为 a_1,a_2,\cdots,a_n,r_i 为 a_1,a_2,\cdots,a_{i-1} 中小于 a_i 的数的数量。
则第 i 位的计算结果为 (a_i-r_i-1)\times (n-i)!。
于是我们做到了 $O(n\log n)$ 算出 $r$ 数组,$O(n)$ 计算答案,总复杂度 $O(n\log n)$。
上代码:
```
#include<bits/stdc++.h>
#define mod 998244353
using namespace std;
int a[1000005];
struct node{
int val;
}tree[1000005<<2];
void pushup(int p){
tree[p].val=tree[p<<1].val+tree[p<<1|1].val;
}
void update(int p,int l,int r,int ql,int qr){
if(l>qr||r<ql)return;
if(l>=ql&&r<=qr){
tree[p].val++;
return;
}
int mid=l+r>>1;
update(p<<1,l,mid,ql,qr);
update(p<<1|1,mid+1,r,ql,qr);
pushup(p);
}
int query(int p,int l,int r,int ql,int qr){
if(l>qr||r<ql)return 0;
if(l>=ql&&r<=qr){
return tree[p].val;
}
int mid=l+r>>1;
return query(p<<1,l,mid,ql,qr)+query(p<<1|1,mid+1,r,ql,qr);
}
int f[1000005];
long long c[1000005];
signed main(){
int n;
cin>>n;
c[1]=1;
c[0]=1;
for(int i=2;i<=1000000;i++){
c[i]=c[i-1]*i%mod;
}
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
f[i]=query(1,1,n,1,a[i]-1);
update(1,1,n,a[i],a[i]);
}
long long ans=0;
for(int i=1;i<=n;i++){
ans+=(a[i]-f[i]-1)*c[n-i];
ans%=mod;
}
cout<<(ans+1)%mod;
return 0;
}
```