题解:P5367 【模板】康托展开

· · 题解

康托展开,即求一个排列在其全排列中,按字典序排序后所处的位置。

如,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,比其更小的数有 12,但 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_nr_ia_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; } ```