zoukankan      html  css  js  c++  java
  • [学习笔记]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 imes 4!)个,同样,如果定下第一个元素是4,对于前两个元素是((4,3)),意味着((4,1),(4,2))为开头的排列的排名比他小,这样的排列有(2 imes 3!)个,以此类推,定下前两个元素,对于前三个元素是((4,3,5))的排列,意味着以((4,3,1),(4,3,2))为开头的元素排名比他小,这样的排列又有(2 imes 2!)个,接着就是((4,3,5,1))为开头,以((4,3,5))为开头的排列没有比他小的,所以最后比当前排列小的排列的个数就是:(3 imes 4!+2 imes 3!+2 imes 2!=88)个,这也就是这个排列的排名/Cantor展开的值。

    实现

    容易发现,一个排列(P=(p_1,p_2,dots,p_n))的Cantor展开(egin{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(nlog n))的复杂度啦。

    一些事项

    Cantor展开其实是一个变进制数,在一些地方似乎能用得上(好像有见过)。

    Cantor展开的值虽然很大,不过感觉更多时候是算系数来解决一些问题。

    逆过程

    都说是一一对应关系,那一个自然数也应该要能映射回去对吧?首先一个具体的数字转换成(n)个系数的表达方式很好做:按照上面的定义,很明显有(a_ileq 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(nlog ^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);
    }
    
  • 相关阅读:
    我到 vim 配置文件---------修改从---http://www.cnblogs.com/ma6174/archive/2011/12/10/2283393.html
    Bayer图像处理
    Ubuntu 12.04下安装OpenCV 2.4.2
    vim寄存器与复制粘贴的实现
    window 驱动
    js 面试题
    angular 写的一个分页功能
    angular scope 方法
    avalon 框架
    贴几个常用的基础函数
  • 原文地址:https://www.cnblogs.com/yoshinow2001/p/14617520.html
Copyright © 2011-2022 走看看