[学习笔记]康托展开

康托展开

一.什么是康托展开

​ 康托展开适用于计算一个排列结果在字典序全排列中的序号(很显然序号与这个排列形成一种双射关系),利用此算法可以构造排列的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;
}
posted @ 2020-01-20 14:43  clockwhite  阅读(613)  评论(0编辑  收藏  举报