康托展开
作者:互联网
公式:\(a_{n}*(n-1)!+a_{n-1}*(n-2)!+……+a_{1}*0!\)
题目:
洛谷P5367 【模板】康托展开
题目描述
求 \(1\sim N\) 的一个给定全排列在所有 \(1\sim N\) 全排列中的排名。结果对 \(998244353\) 取模。
输入格式
第一行一个正整数 \(N\)。
第二行 \(N\) 个正整数,表示 \(1\sim N\) 的一种全排列。
输出格式
一行一个非负整数,表示答案对 \(998244353\) 取模的值。
输入输出样例
输入
3
2 1 3
输出
3
输入
4
1 2 4 3
输出
2
说明/提示
对于\(10\%\)数据,\(1\le N\le 10\)。
对于\(50\%\)数据,\(1\le N\le 5000\)。
对于\(100\%\)数据,\(1\le N\le 1000000\)。
对于这道题,康托展开的公式中\(a[i]\)是数列中从后往前数第\(1\sim i-1\)个数中比第i个数小的数的个数,整个运算过程也都是从后往前进行。
证明:
数列的第一个数,即最后一个被运算的数,当这个位置被确定时,后面的排列还有\((n-1)!\)种方式,而确保排在这种排列前面的排列一定是数比这个数小的数,共\(a[n]\)个,即它一定排在\(a[n]*(n-1)!\)种排列方式后,计算下一个位置时,同理,但因为前面的数已经确定了,所以\(a[i]\)只考虑它后面的数,其中求比这位数小数的个数可以用权值线段树。
代码:
#include<iostream>
#define mod 998244353
#define int long long
using namespace std;
int n;
int a[1000010];
struct node{
int l,r;
int sum;
}tree[4000010];
int tot=1;
void build(int i,int l,int r){
tree[i].l=l;
tree[i].r=r;
if(l==r){
tree[i].sum=0;
return ;
}
int mid=(l+r)/2;
build(i*2,l,mid);
build(i*2+1,mid+1,r);
tree[i].sum=0;
}
void add(int i,int p){
tree[i].sum++;
if(tree[i].l==tree[i].r){
return ;
}
if(tree[i*2].r>=p){
add(i*2,p);
}
else{
add(i*2+1,p);
}
return ;
}
int search(int i,int l,int r){
if(tree[i].l>=l&&tree[i].r<=r){
return tree[i].sum;
}
if(tree[i].r<l||tree[i].l>r){
return 0;
}
int s=0;
if(tree[i*2].r>=l){
s+=search(i*2,l,r);
}
if(tree[i*2+1].l<=r){
s+=search(i*2+1,l,r);
}
return s;
}
int ans=0;
signed main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
build(1,1,n);
for(int i=n-1;i>=1;i--){
add(1,a[i+1]);
tot=tot*(n-i)%mod;
ans=(ans+tot*search(1,1,a[i]-1)%mod)%mod;
}
cout<<ans+1;
return 0;
}
标签:le,return,int,sum,tree,add,展开,康托 来源: https://www.cnblogs.com/z-2-we/p/16171342.html