洛谷P5367 【模板】康托展开
题目链接:https://www.luogu.com.cn/problem/P5367
康拓展开:建立起排列和自然数的双射关系,可以用来求解排列的哈希值
设给定一个排列 \(a\),则 \(a\) 在排列集合中的排名为:
\[ans=∑_{i=1}^npi∗(n−i)!
\]
其中 \(p_i\) 表示小于 \(a[i]\) 的没有出现过的数的次数 (可以用树状数组求解)
时间复杂度 \(O(nlogn)\)
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<stack>
#include<queue>
using namespace std;
typedef long long ll;
const int maxn = 1000100;
const int M = 998244353;
int n;
int a[maxn], c[maxn];
int fac[maxn];
void add(int k, int x){
for(; x <= n ; x += x & (-x)){
c[x] += k;
}
}
int query(int x){
int res = 0;
for(; x ; x -= x & (-x)){
res += c[x];
}
return res;
}
ll read(){ ll s=0,f=1; char ch=getchar(); while(ch<'0' || ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0' && ch<='9'){ s=s*10+ch-'0'; ch=getchar(); } return s*f; }
int main(){
n = read();
fac[0] = 1;
for(int i = 1 ; i <= n ; ++i){ fac[i] = 1ll * fac[i - 1] * i % M; }
for(int i = 1 ; i <= n ; ++i) add(1, i);
// printf("%d\n",query(n));
for(int i = 1 ; i <= n ; ++i) a[i] = read();
int ans = 1;
for(int i = 1 ; i <= n ; ++i){
int p = 1ll * (query(a[i]) - 1) * fac[n - i] % M;
ans = (ans + p) % M;
add(-1, a[i]);
}
printf("%d\n", ans);
return 0;
}
逆康托展开
将求解康托展开的过程反过来,每次对 \(ans\) 除和模 \((n-i)!\) 即可
需要求解动态第 \(k\) 小元素,使用平衡树