[学习笔记]康托展开
康托展开
一.什么是康托展开
康托展开适用于计算一个排列结果在字典序全排列中的序号(很显然序号与这个排列形成一种双射关系),利用此算法可以构造排列的Hash表。
二.实现康托展开
举个例,在集合\(\{1,2,3\}\)中选取三个数的全排列,问 3,2,1 是第几个。
对于这个问题,如果说让我用手来算的话,我会使用分类讨论比他小的排列,见下图。
至于累积的结果是否需要+1,取决于编号从0还是1开始。大致总结一下,我们可以得出康托展开的一般形式。
\[ans=\sum^{n}_{i=1}p_i*(n-i)!
\]
其中\(P_i\)为在未选取的元素中比排列结果中的第i个数小的元素个数。在下文称之为系数。
三.求解系数
很简单的,我们能过想到接近于\(O(n^2)\)复杂度的暴力与vector求解,但是我们需要一个更高级的算法。
如果说我们能用0,1表示某个元素是否存在,(我们使得初始集合中的元素成升序排列),那么对于前缀和而言,所代表的涵义就是比他小的元素个数,于是用此方法就可以快速求解系数。当这个数选取了之后,我们需要把它标记为不存在,前缀和涉及到修改,我们可以用树状数组求解。
四.Code(Luogu板子)
ll mod=998244353;
ll n,c[1000005];
void add(int x,int y){
for(;x<=n;x+=x&(-x))c[x]+=y;
}
int sum(int x){
ll res=0;
while(x>0){
res+=c[x];
res%=mod;
x-=x&(-x);
}
return res-1;
}
ll now[1000005],ans,jc[1000005]={1};
void ready(){
for(int i=1;i<=n;++i)jc[i]=(jc[i-1]*i)%mod;
}
int main(){
cin>>n;
ready();
for(int i=1;i<=n;++i){
cin>>now[i];
add(i,1);
}
for(int i=1;i<=n;++i){
ans=(ans+jc[n-i]*sum(now[i]))%mod;
add(now[i],-1);
}
cout<<(ans+1)%mod;
return 0;
}
五.逆康托展开
在知道了康托展开的原理之后,我们其实可以通过序号求解排列(前文有提到他们是双射关系),于是考虑求解。
我们可以知道一个东西(易证得)
\[P_k(n-k)!>\sum^{k-1}_{i=1}P_i(n-i)!
\]
于是我们对每一个\((n-i)!\)进行取模与除就可以反向推出每一项的系数。得到系数以后,由于系数即代表大小关系,所以令\(i=P_i+1\),之后我们需要通过一个算法来求出第i小的元素,输出并删除,这里我用的是平衡树(FHQ)
挂一个已知系数求解元素的代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=50010;
int size_node[N],children[N][2],number_tree[N],number_heap[N],total,root, number_data_sets,order,x;
void update(int node_now) {size_node[node_now]=size_node[children[node_now][0]]+size_node[children[node_now][1]]+1;}
void split(int node_now,int k,int &tree_accept,int &tree_unaccept) {
if(!node_now){
tree_accept=tree_unaccept=0;
return;
}
if(number_tree[node_now]>k) {
tree_unaccept=node_now;
split(children[node_now][0],k,tree_accept,children[node_now][0]);
} else {
tree_accept=node_now;
split(children[node_now][1],k,children[node_now][1],tree_unaccept);
}
update(node_now);
}
int node_add(int k) {number_heap[++total]=rand(),number_tree[total]=k,size_node[total]=1;return total;}
int merge(int tree_one,int tree_two) {
if(!tree_one||!tree_two)return tree_one+tree_two;
if(number_heap[tree_one]<number_heap[tree_two]) {
children[tree_one][1]=merge(children[tree_one][1],tree_two);
update(tree_one);
return tree_one;
} else {
children[tree_two][0]=merge(tree_one,children[tree_two][0]);
update(tree_two);
return tree_two;
}
}
void Insert(int k) {
int tree_one,tree_two;
split(root,k,tree_one,tree_two);
root=merge(merge(tree_one,node_add(k)),tree_two);
}
void extrack(int k) {
int tree_less,tree_unless,tree_k;
split(root,k,tree_less,tree_unless);
split(tree_less,k-1,tree_less,tree_k);
tree_k=merge(children[tree_k][0],children[tree_k][1]);
root=merge(tree_less,merge(tree_k,tree_unless));
}
int frank(int node_now,int k) {
while(1) {
if(size_node[children[node_now][0]]>=k)node_now=children[node_now][0];
else if(size_node[children[node_now][0]]+1<k)k-=size_node[children[node_now][0]]+1,node_now=children[node_now][1];
else return number_tree[node_now];
}
}
ll k,n,s[N];
int main(){
cin>>k;
while(k--){
total=0;
scanf("%d",&n);
for(int i=1;i<=n;++i){
Insert(i);
cin>>s[i];
}
for(int i=1;i<=n;++i){
int u=frank(root,s[i]+1);
printf("%d ",u);
extrack(u);
}
printf("\n");
}
return 0;
}