康拓展开
首先康拓展开是一个求几个数的一个排列在全排列的位置的一个算法。
举个例子:
[3.1.2]的全排列为:
[1.2.3]
[1.3.2]
[2.1.3]
[2.3.1]
[3.1.2]
[3.2.1]
这六个排序方式,其中\([3.1.2]\)不难看出来是在其全排列的第五位,但随着数字的增多,排列方式呈极快的方式增加,然后就没有然后了(主要就是无法手动打表了),然后就要引出我们的主题——康拓展开。
康拓展开原理:
敲黑板
根据上面的例子,不难发现\([3.1.2]\)的第一位为\(3\),比\(3\)小的数有两个\((1.2)\),又因为其为第一位,身后有两位\((1.2)\),所以\(3\)对答案的贡献为\(2*2!\),以此类推,\(1\)对答案的贡献为\(0*1!\),\(2\)对答案的贡献为\(0*0!\)(0!==1),然后此时答案的总和为[3.1.2]前面总共有多少种排列,此时将答案++,即可得到[3.1.2]在全排列中的位置。
再来一个例子
求[2.3.4.5.1]在全排列中的位置。
首先根据上述的做法,不难发现\(2\)对答案的贡献为\(1*4!\),\(3\)对答案的贡献原本应该是\(2*3!\),但由于\(2\)在前一个位置已经被使用过,所以\(3\)的真实贡献为\(1*3!\),以此类推\(4\)的贡献为\(1*2!\),\(5\)的贡献为\(1*1!\),\(1\)的贡献为\(0*0!\),所以[2.3.4.5.1]在全排列中的位置为\(34\)。
逆康拓展开:
根据字面的意思不难理解,就是给出一个数,然后求出原排列的数。
例子:
给定\(1.2.3.4.5\),以及一个数(如96),求\(96\)所对应的排序。
首先我们需要\(96-1\)得到这个排序前有多少个排序,易得为\(95\),然后第一位后有四个数字,所以将\(95/4!\),得到\(3......23\),所以得到第一位为\(3\),然后用余数去除以剩余的位数,以此类推,易得到,其为[4.5.3.2.1]。
【模板】康托展开
题目描述
求\(1\sim N\)的一个给定全排列在所有\(1\sim N\)全排列中的排名。结果对\(998244353\)取模。
输入输出格式
输入格式
第一行一个正整数 \(N\)。 第二行 \(N\) 个正整数,表示 \(1\sim N\) 的一种全排列。
输出格式
一行一个非负整数,表示答案对 \(998244353\) 取模的值。
输入输出样例
输入样例 #1
3
2 1 3
输出样例 #1
3
输入样例 #2
4
1 2 4 3
输出样例 #2
2
说明
对于\(10\%\)数据,\(1\le N\le 10\)。 对于\(50\%\)数据,\(1\le N\le 5000\)。 对于\(100\%\)数据,\(1\le N\le 1000000\)。
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
ll tree[1000005];
int n;
int lowbit(int x){
return x&-x;
}
void update(int x,int y){
while(x<=n){
tree[x]+=y;
x+=lowbit(x);
}
}
ll query(int x){
ll sum=0;
while(x){
sum+=tree[x];
x-=lowbit(x);
}
return sum;
}
const ll mod=998244353;
ll jc[1000005]={1,1};
int a[1000005];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
jc[i]=(jc[i-1]*i)%mod;
update(i,1);
}
ll ans=0;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
ans=(ans+((query(a[i])-1)*jc[n-i])%mod)%mod;//计算ans
update(a[i],-1);
}
printf("%lld",ans+1);
return 0;
}