• 康托展开 & 逆康托展开


    康托展开 & 逆康托展开

    定义

    康托展开是一个全排列到一个自然数的双射,常用于构建hash表时的空间压缩

    设有(n)个数((1,2,3,4,dots ,n)),组成不同(n!) 种的排列组合,其康托展开唯一且最大约为(n!)

    康托展开表示的就是当前排列在(n)个不同元素的全排列中的名次。

    时间复杂度(O(n^2))

    适用范围

    搜索,动态规划中常常用一个数字来表示一种状态,大大降低空间复杂度

    公式

    (X=a_1 imes(n−1)!+a_2 imes(n−2)!+⋯+a_n imes0!)

    (X) 代表当前排列在全排列中的排名

    (a_i) 代表当前数是数列中未出现的数中第几小的 从0开始计数,0是第一小的数

    例如 $ 4,2,3 ,1 $

    (4) 是当前数列中未出现的数中第(3) 小的,(X += 3*(4-1)!)

    (2) 是当前数列中未出现的数中第(1) 小的,(X += 1*(4-2)!)

    (3) 是当前数列中未出现的数中第(1) 小的,(X+=1*(4-3)!) ,因为(2) 已经输出过了,所以不算

    (1) 是当前数列中未出现的数中第(0) 小的,(X += 0*(4-4)!)

    这要就求出了(4,2,3 ,1) 所唯一对应的在全排列中的名次(X = 22)

    • 注意到我们每次要用到 当前有多少个小于它的数还没有出现
    • 这里用树状数组统计比它小的数出现过的次数就可以了,可以优化到(O(nlog{n}))

    代码

    先预处理阶乘

    void init() {
        fact[0] = 1;
        for(int i = 1;i <= 9; ++i) fact[i] = fact[i - 1] * i;
        // 递推求阶乘
    }
    // 或者直接打表
    int fact[10] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
    

    (cantor)函数

    int cantor(int a[],int n) {
        int res = 0;
        for(int i = 0;i < n; ++i) {
            int cnt = 0;
            for(int j = i + 1;j < n; ++j) if(a[j] < a[i]) cnt ++;
            // 找到a[i]是当前数列中未出现的数中第几小的
            // 从1开始,即1-n的全排列
            // 从0开始,就变成了0-n的全排列,记得变通
            res += cnt * fact[n - i - 1];// 累加值
        }
        return res + 1;// 如果输出的是排名就要 + 1,如果是hash值可以直接返回 res
    }
    

    逆康托展开

    因为排列的排名和排列是一一对应的,所以康托展开满足双射关系,是可逆的。

    可以通过类似上面的过程倒推回来。

    首先把排名(X) 减去(1) ,变成以(0) 开始的排名

    例如求 (1,2,3,4) 的全排列序列中,排名第(22) 的序列是什么

    (22 - 1 = 21)(21) 代表着有多少个排列比这个排列小

    第一个数 (a[1])

    (lfloor{21/(4-1)!} floor = 3) 比$a[1] $ 小且没有出现过的数有(3) 个,(a[1]=4)

    (X=Xmod3 imes(4-1)!=3)

    第二个数(a[2])

    (lfloor{3/(4-2)!} floor=1)(a[2]) 小且没有出现过的数有(1) 个,所以(a[2]=2)

    (X=Xmod1 imes(4-2)!=1)

    第三个数(a[3])

    (lfloor{1/(4-3)!} floor=1)(a[3]) 小且没有出现过的数有(1) 个,所以(a[3]=3)

    (X=Xmod1 imes(4-3)!=0)

    第四个数(a[4])

    (lfloor{0/(4-4)!} floor=0)(a[4]) 小且没有出现过的数有(0) 个,所以(a[4]=1)

    最终得到数列(4,2,3,1)

    代码

    vector<int> incantor(int x,int n) {
        x--;// 得到以0开始的排名
        vector<int> res(n);// 保存数列答案
        int cnt;
        bool st[10];// 标记数组
        memset(st,0,sizeof st);
        for(int i = 0;i < n; ++i) {
            cnt = x/fact[n - i - 1];// 比a[i]小且没有出现过的数的个数
            x %= fact[n - i - 1];// 更新 x
            for(int j = 1;j <= n; ++j) {// 找到a[i],从1开始向后找
                if(st[j]) continue;// 如果被标记过,就跳过
                if(!cnt) {// 如果cnt == 0说明当前数是a[i]
                    st[j] = 1;//标记
                    res[i] = j;// 第i位是j
                    break;
                }
                cnt --;// 如果当前不是0,就继续往后找
            }
        }
        return res;// 返回答案
    }
    
  • 相关阅读:
    【应试】数据通信与网络
    【应试】操作系统OS
    【笔记】 卷积
    【HDU 5920】 Ugly Problem
    【笔记】位运算
    【洛谷P1378】油滴扩展
    【洛谷 P1120】 小木棍[数据加强版]
    [codeforces]Round #538 (Div. 2) F. Please, another Queries on Array?
    [BZOJ]2563: 阿狸和桃子的游戏
    [BZOJ]4668: 冷战
  • 原文地址:https://www.cnblogs.com/lukelmouse/p/12443311.html
Copyright © 2020-2023  润新知