[学习笔记]Cantor展开/CF501D
Cantor展开
这是大概半年前学到的trick,今天突然想起来就来复习一下。
我们知道对于\(n\)个数\(1,2,\dots,n\)的排列一共有\(n!\)个,同时我们很容易定义两个不同排列\(p_1,p_2\)的大小关系——按字典序比较就行,于是我们很自然地会发现,对于任意一个排列\(P=(p_1,p_2,\dots,p_n)\),应该是能够和自然数建立一一对应的关系的——依靠这个排列在所有排列里字典序的排名。
Cantor展开就是做这件事情,我们定义最小的排列\(1,2,3,4,5\)的排名是0,接着往下\(1,2,3,5,4\)的排名就是1,Cantor展开的值也就是1。
举个栗子
对于任意一个排列\(P\)我们也容易算出他的排名:
以\(4,3,5,1,2\)为例,第一个元素是\(4\)意味着以\(1,2,3\)为开头的排列的排名都一定比他小,这样的排列有\(3\times 4!\)个,同样,如果定下第一个元素是4,对于前两个元素是\((4,3)\),意味着\((4,1),(4,2)\)为开头的排列的排名比他小,这样的排列有\(2\times 3!\)个,以此类推,定下前两个元素,对于前三个元素是\((4,3,5)\)的排列,意味着以\((4,3,1),(4,3,2)\)为开头的元素排名比他小,这样的排列又有\(2\times 2!\)个,接着就是\((4,3,5,1)\)为开头,以\((4,3,5)\)为开头的排列没有比他小的,所以最后比当前排列小的排列的个数就是:\(3\times 4!+2\times 3!+2\times 2!=88\)个,这也就是这个排列的排名/Cantor展开的值。
实现
容易发现,一个排列\(P=(p_1,p_2,\dots,p_n)\)的Cantor展开\(\begin{aligned}X=\sum_{i=1}^n a_i (n-i)!\end{aligned}\),其中\(a_i\)表示有多少个\(t\)使得\((p_1,\dots,p_{i-1},t)>(p_1,\dots,p_i)\)这个排列比原排列\(P\)来的大,因为前面用过的已经不能用了,所以这个\(t\)的个数其实就是在\(p_{i+1},\dots,p_n\)里比\(p_i\)小的数的个数,这可以用一个树状数组来维护,于是求一个排列Cantor展开的系数就可以做到\(O(n\log n)\)的复杂度啦。
一些事项
Cantor展开其实是一个变进制数,在一些地方似乎能用得上(好像有见过)。
Cantor展开的值虽然很大,不过感觉更多时候是算系数来解决一些问题。
逆过程
都说是一一对应关系,那一个自然数也应该要能映射回去对吧?首先一个具体的数字转换成\(n\)个系数的表达方式很好做:按照上面的定义,很明显有\(a_i\leq n-i\),对于\(t=0,1,\dots,k-1\),有\(k!>t(k-1)!\),所以如果我们只有一个\(X,n\),每次求\(a_i\)的时候,\(a_i=X/(n-i)!\),再用\(X mod(n-i)!\)来更新\(X\)就行。
接下来就是要怎么通过系数求出具体的排列了,依然用前面的栗子,\((4,3,5,1,2)\)生成的Cantor展开的系数应该是\((3,2,2,0,0)\),第一个3意味着\(\{1,2,3,4,5\}\)里有3个比第一个元素小的,那么第一个元素就是4,接着我们定下来4,再从\(\{1,2,3,5\}\)里选排名=2+1=3的3,第二个元素就是3,接着往下,从\(\{1,2,5\}\)里选排名第三小的,也就是5,接着是\(\{1,2\}\)里选排名第0+1=1的1…
一个比较简单的实现就还是一个树状数组:值域树状数组,一开始全部设为1,每次二分找到最小的前缀和超过\(a_i\)的位置,就是这个位置上的答案啦,这样做是\(O(n\log ^2 n)\)的,如果要优化掉这个\(\log\)那就是直接在线段树上二分啦。
CF501D
给两个排列\(p,q\),\(Ord(p)\)表示一个排列\(p\)的排名,求排名为\((Ord(p)+Ord(q))mod (n!)\)的排列。
就是一个裸的Cantor展开啦:
rep(i,1,n)modify(i,1);
rep(i,1,n){
f[i]=query(a[i]);
modify(a[i]+1,-1);
}
rep(i,1,n)modify(i,1);
rep(i,1,n){
f[i]+=query(b[i]);
modify(b[i]+1,-1);
}
for(int i=n;i>=1;i--)if(f[i]>n-i){
f[i-1]++;
f[i]%=n-i+1;
}
rep(i,1,n)modify(i,1);
rep(i,1,n){
int l=1,r=n,ret=-1;
while(l<=r){
int mid=(l+r)>>1;
int q=query(mid);
if(q<f[i]+1)l=mid+1;
else{
r=mid-1;
ret=mid;
}
}
modify(ret,-1);
printf("%d ",ret-1);
}