洛谷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\) 小元素,使用平衡树

posted @ 2020-11-23 14:42  Tartarus_li  阅读(103)  评论(0编辑  收藏  举报