康托展开
有n个数\(1\sim n\),显然,我们知道,其有\(n!\)种排列。那么,从小到大排序这些排列,能否求出,某个排列是第几个?
解决上述问题,我们就要用到康托展开。
康托展开
内容
康托展开能够计算出:对于一个1到n的排列\(\left\{a_1,a_2,a_3,…,a_n\right\}\),比它小的排列有多少个。
举个例子,假设\(n=4\),给定的排列是\(\left\{2,3,1,4\right\}\)(后简称为a)。那么,由于排序是逐位比较的,所以我们也逐位分析:比a小的排列是从哪一位开始比a小的?
- 第一位是2,比2小的数只有1,所以以1为开头的所有排列一定比a小,共有\(1\times 3!=6\)种(1表示只有1一种情况比2小,\(3!\)表示后面三个数字可以任意排列的方案数);
- 第二位是3,比3小的数有1,2,又因为2在第一位,故只有一种情况1,即这种情况下只有以2,1开头才能比a小,共\(1\times 2!=2\)种;
- 第三位是1,没有比1小的数了,所以不可能存在排列,使得从第三位开始才小于a,故有\(0\times1!=0\)种;
- 第四位是4,比4小的有1,2,3,但是它们在前几位都被用了,所以也不存在,共\(0\times0!=0\)种。
共\(6+2+0+0 = 8\)个排列比a小,故a是第9名。
所以,我们可以推出对于任意一个排列,有多少个排列比它小:
其中\(sum_{a_i}\)表示\(a_i\)后面有多少个数小于它,即\(sum_{a_i} = \sum\limits_{j=i}^{n}{(a_j<a_i)}\)
解释如下:
逐位比较,从这一位开始比a小的情况(即前几位都与a一样,这一位比a的这一位小)共有\(sum_{a_i}\)种,因为在\(a_i\)前面的不能改变,故比a小只能是在\(a_i\)以后的、比\(a_i\)小的数。剩下还有\((n-i)\)位,剩下的数字可以随便填(因为这一位已经小了,其余位就没关系了),共\((n-i)!\)种。根据乘法原理,这一位开始比a小的情况就有\(sum_{a_i}*(n-i)!\)中;根据加法原理,总情况就有\(\sum\limits_{i=1}^{n}{sum_{a_i}*{n-i}!}\)种。
代码
根据式子,我们很容易就能写出代码:
阶乘的部分,我们可以用 j(i)c(an) 数组预处理得到,而sum数组,则可以根据定义进行模拟,代码如下:
例题:P2524 Uim的情人节礼物·其之弐
#include<iostream>
#include<cstdio>
#define maxn 1000000
#define mod 998244353
#define ll long long
using namespace std;
ll n,a[maxn],jc[maxn],sum=0,tot=0;
int main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%1lld",&a[i]);
}
jc[1]=1;
for(int i=2;i<=n;i++){
jc[i]=(jc[i-1]*i)%mod;
}
for(int i=1;i<=n;i++){
tot=0;
for(int j=i+1;j<=n;j++)
if(a[j]<a[i]) tot++;
sum+=(tot*jc[n-i])%mod;
}
printf("%lld",sum+1);
return 0;
}
不难看出,复杂度是\(O(n^2)\),过这道题是绰绰有余了,但是,如果数据范围再大一些呢?
我们发现,能优化的只能是求sum数组的部分了。我们发现,能够用树状数组解决这个问题:
首先定义x数组均为1,用树状数组c维护x数组。每搜过一位后,就把x数组的这一位\(x_{a_i}\)设为0,这样,\(sum_{a_i}\)就等于\(\sum\limits_{j=1}^{a_i-1}x_j\)了,即在\(a_i\)前面且没搜过的数的个数(因为搜过的都变成0了)。所以,只需要一个单点修改区间查询的树状数组维护即可。
例题:P5367 【模板】康托展开
#include<iostream>
#include<cstdio>
#define maxn 1000005
#define mod 998244353
#define ll long long
using namespace std;
ll n,a[maxn],jc[maxn],sum=0,tot=0;
ll c[maxn];
ll lowbit(ll x) {return x&(-x);}
ll pluss(ll p,ll x) {for(ll i=p;i<=n;i+=lowbit(i)) c[i]=(c[i]+x)%mod;}//单点修改
ll search(ll ri) {ll res=0;for(ll i=ri;i>=1;i-=lowbit(i)) res=(res+c[i])%mod;return res%mod;}//区间查询
int main(){
scanf("%lld",&n);
for(ll i=1;i<=n;i++){
scanf("%lld",&a[i]);
pluss(i,1);
}
jc[0]=jc[1]=1;
for(ll i=1;i<=n;i++){
jc[i]=(jc[i-1]*i)%mod;
}
for(ll i=1;i<=n;i++){
sum=(sum+((search(a[i]-1)*jc[n-i])%mod))%mod;
pluss(a[i],-1);
}
printf("%lld",(sum+1)%mod);
return 0;
}

浙公网安备 33010602011771号