康托展开是一个全排列到一个自然数的双射
即可以计算出 1 ~ n的全排列中字典序为k的排列,也可以计算出给定的排列在全排列中的排名
康托展开
康托展开计算一个给定的 1 ~ n 排列的排名的公式为
1
+
∑
i
=
1
n
A
i
(
n
−
i
)
!
1+\sum_{i=1}^n A_i(n-i)!
1+∑i=1nAi(n−i)!
其中
A
i
A_i
Ai表示该排列中第i位后比第i位数字小的数的个数
即假设该排列前 1 ~ i 位不变
如果把第i位后比第i位小的数与第i位交换
那么第 i+1 ~ n 位的数无论如何排列,最后得到的排列都会比原排列小
即总共有
A
i
(
n
−
i
)
!
A_i(n-i)!
Ai(n−i)!种排法
每一位都如此计算后相加就得到排名在该排列之前的个数
#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long lt;
#define lowbit(x) ((x)&(-x))
int read()
{
int f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
const int mod=998244353;
const int maxn=2000010;
int n;
int a[maxn],fac[maxn],sum[maxn];
void add(int x,int v){ for(int i=x;i<=n;i+=lowbit(i)) sum[i]+=v;}
int qsum(int x){ int res=0; for(int i=x;i>0;i-=lowbit(i))res+=sum[i]; return res;}
int main()
{
n=read();
for(int i=1;i<=n;++i)
a[i]=read();
fac[0]=1;
for(int i=1;i<=n;++i)
fac[i]=1ll*fac[i-1]*i%mod;
int ans=0;
for(int i=n;i>=1;--i)
{
ans=(ans+1ll*fac[n-i]*qsum(a[i])%mod)%mod;
add(a[i],1);
}
printf("%d\n",ans+1);
return 0;
}
逆康托展开
给定排名求排列就是康托展开的逆向过程
对第i位的数,以排名除以
(
n
−
i
)
!
(n-i)!
(n−i)!得到余数r和商q
那么第i位就是剩余数字中第q+1小的值
之后以余数r继续作为排名计算下一位
比如 1 ~ 4 的排列中排名为10的
即所求排列前面有9个字典序更小的排列
9/3! 得 q=1 ,r=3,说明所求排列第一位之后只有一个比第一位小的,所以第一位是2
3/2! 得 q=1 ,r=1,说明第二位之后只有一个更小的数,即剩余数(1,3,4)中第2小的,所以第二位是3
1/1! 得 q=1, r=0,说明第三位之后也只有一个更小的数,所以第三位是4
最后一位只剩下1
所以所求排列即 2 3 4 1
UVA - 11525 Permutation
这题给的排名计算公式恰好就是康托展开
所以直接用treap等维护找出剩余数字第s+1小即可
#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long lt;
#define lowbit(x) ((x)&(-x))
int read()
{
int f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
const int maxn=100010;
int T,n;
int val[maxn],rnd[maxn];//,fa[maxn];
int ch[maxn][2],size[maxn],rt,tot;
void update(int p){ size[p]=size[ch[p][0]]+size[ch[p][1]]+1;}
void rotate(int &p,int d)
{
int k=ch[p][d^1];
ch[p][d^1]=ch[k][d];
ch[k][d]=p;
update(p); update(k);
p=k;
}
void ins(int &p,int x)
{
if(!p){
p=++tot; val[p]=x;
rnd[p]=rand(); size[p]=1;
return;
}
int d=x<val[p]?0:1;
ins(ch[p][d],x);
if(rnd[ch[p][d]]<rnd[p]) rotate(p,d^1);
update(p);
}
void del(int &p,int x)
{
if(x==val[p])
{
if(!ch[p][0]) p=ch[p][1];
else if(!ch[p][1]) p=ch[p][0];
else
{
int dd=rnd[ch[p][0]]<rnd[ch[p][1]]?1:0;
rotate(p,dd); del(ch[p][dd],x);
}
}
else if(x<val[p]) del(ch[p][0],x);
else del(ch[p][1],x);
if(p) update(p);
}
int kth(int p,int k)
{
int ss=size[ch[p][0]];
if(k==ss+1) return val[p];
else if(k<=ss) return kth(ch[p][0],k);
else return kth(ch[p][1],k-ss-1);
}
void init()
{
rt=tot=0;
memset(size,0,sizeof(size));
memset(ch,0,sizeof(ch));
}
int main()
{
T=read();
while(T--)
{
init(); n=read();
for(int i=1;i<=n;++i) ins(rt,i);
for(int i=1;i<=n;++i)
{
int s=read();
int x=kth(rt,s+1);
del(rt,x);
printf("%d",x);
if(i!=n) printf(" ");
}
printf("\n");
}
return 0;
}