@
前言本蒟刚刚学了基本的康托展开,特在此留下学习笔记。
康托展开是什么?
康托展开就是求在一个特定长度的全排列中,一个特定的排列在全部排列中排第几个。举个例子:如长度为\(3\)的全部排列为
\(1,2,3\)
\(1,3,2\)
\(2,1,3\)
\(2,3,1\)
\(3,1,2\)
\(3,2,1\)
所以\(1,2,3\)就排第\(1\)个,\(1,3,2\)就排第\(2\)个\(……\)
康托展开就是能够快速求出这个“排名”的算法。
其中\(x_i\)为在\(a_1 \sim a_{i-1}\)当中有多少个数比小。也就是说,\(x_i\)表示有多少在他前面的数比它小。
说得我自己都\(懵\)了,让我举个栗子来详细说明一下。
假如要求的排列为\(3,1,2\)
因为\(x_i\)表示有多少在他前面的数比它小,所以公式第一项\(x_n\)为\(a_n\)前面比它大的数的数量,因为\(a_n\)为\(3\)(\(a_n\)表示第一个数),所以说\(x_n\)为第\(n\)个位置前比\(a_n\)大的数的数量即\(2\)(\(1,2\)都比\(3\)小),然后再乘\((n-1)!\)即\(1 * 2=2\)所以该公式第一项\(x_n*(n-1)!=2*2=4\)。
以此类推,第二项为\(x_{n-1}*(n-2)!=0*1=0\),第三项也为\(0\)。所以说\(ans=4+0+0+1=5\)。
至此,我们就求出了\(3,1,2\)的“排名”为\(5\)。
下面是\(1,2,3\)全排列的全部计算公式
序号 | 数列 | 计算公式 |
---|---|---|
\(1\) | \(1,2,3\) | \(0*2!+0*1!+0*0!+1=1\) |
\(2\) | \(1,3,2\) | \(0*2!+1*1!+0*0!+1=2\) |
\(3\) | \(2,1,3\) | \(1*2!+0*1!+0*0!+1=3\) |
\(4\) | \(2,3,1\) | \(1*2!+1*1!+0*0!+1=4\) |
\(5\) | \(3,1,2\) | \(2*2!+0*1!+0*0!+1=5\) |
\(6\) | \(3,2,1\) | \(2*2!+1*1!+0*0!+1=6\) |
#include<bits/stdc++.h>
#define mod 998244353
#define sco 1000010
#define ll long long
#define rg register
#define getcha() (S==T&&(T=(S=fsr)+fread(fsr,1,1<<15,stdin),T==S)?EOF:*S++)
using namespace std;
ll n(0),ans(1),a[sco],jc[sco]={0,1};
char fsr[1<<15],*S=fsr,*T=fsr;
inline ll read() {
rg ll r(0);
rg char ch;
while(ch=getcha(),ch>=58 || ch<=47);
r=r*10+ch-48;
while(ch=getcha(),ch<=57 && ch>=48)r=r*10+ch-48;
return r;
}
signed main(){
n=read();
for(rg ll i=1;i<=n;++i)a[i]=read();
for(rg ll i=2;i<=n;++i)jc[i]=jc[i-1]*i;
for(rg ll i=1;i<n;++i){
rg ll x=0;
for(rg ll j=i+1;j<=n;++j){
if(a[j]<a[i])++x;x%=mod;
}
ans=(ans+(x*jc[n-i]))%mod;
}
printf("%lld",ans);
return 0;
}