@

前言

本蒟刚刚学了基本的康托展开,特在此留下学习笔记。


康托展开是什么?

  康托展开就是求在一个特定长度的全排列中,一个特定的排列在全部排列中排第几个。举个例子:如长度为\(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\)\(……\)
康托展开就是能够快速求出这个“排名”的算法。

康托展开公式

\[ans=x_n*(n-1)!+x_{n-1}*(n-2)!+……+x_2*1!+x_1*0!+1 \]

  其中\(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\)
求康托展开的\(Code\)
#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;
}