• 康托展开(树状数组逆序对优化)


    概述

    康托展开是一个全排列到一个自然数的双射。设有n个数(1,2,3,4,…,n),可以有组成不同(n!种)的排列组合,康托展开表示的就是是当前排列组合在n个不同元素的全排列中的名次。

    康托展开

    公式

    (X = a[n] * (n - 1)! + a[n - 1] * (n - 2)! + a[n - 2] * (n - 3)! …… + a[2] * 1! + a[1] * 0!)

    例子

    例如3的全排列

    • 123 (0 * 2! + 0 * 1! + 0 * 0! = 0)
    • 132 (0 * 2! + 1 * 1! + 0 * 0! = 1)
    • 213 (1 * 2! + 0 * 1! + 0 * 0! = 2)
    • 231 (1 * 2! + 1 * 1! + 0 * 0! = 3)
    • 312 (2 * 2! + 0 * 0! + 0 * 0! = 4)
    • 321 (2 * 2! + 1 * 1! + 0 * 0! = 5)

    样例解释

    估计大家应该不用解释都应该明白了,这里的a[i]系数是如何来的了

    • 321 (2 * 2! + 1 * 1! + 0 * 0!)

    3 > 2, 3 > 1,得到a[3] = 2,2 > 1,得到a[2] = 1
    其中的康托展开的数值代表的是当前项在全排类中的数值下标(注意下标从0开始计数)

    康托展开代码

    /*
    	Code by lifehappy 2020:04:21
    	康托展开计算O(n * n)
    	未优化,明天把优化的代码补上。
    */
    #include<bits/stdc++.h>
    using namespace std;
    int fac[10] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};//阶乘
    int cantor(int a[], int n) {
        int s = 0;
        for(int i = 0; i < n; i++) {
    	int num = 0;
    	for(int j = i + 1; j < n; j++)
    	    if(a[i] > a[j])
    		num++;
    	s += num * fac[n - i - 1];
        }
        return s;
    }
    int main() {
        int a[5] = {4, 5, 3, 1, 2}, n = 5;
        printf("%d
    ", cantor(a, n));
        return 0;
    }
    
    94
    

    逆康托展开

    也就是康托展开的逆过程,就拿上面的例子来说

    排列4 5 3 1 2的康托展开值是94。

    逆运算计算过程

    • 94 / 4! = 3 余 22,从(1 2 3 4 5)中得到第四大的为当前位的数字(4)
    • 22 / 3! = 3 余 4,从(1 2 3 5) 中得到第四大的为当前位的数字(5)
    • 4 / 2! = 2 余 0,从(1, 2, 3) 中得到第三大的为当前位的数字(3)
    • 0 / 1!= 0 余 0,从(1, 2) 中得到第二大的为当前位的数字(1)
    • 直到剩下最后一位

    逆运算代码

    /*
    	Code by lifehappy 2020:04:21
    	逆康托展开计算O(n * n)
    	康托展开项未优化
    */
    #include<bits/stdc++.h>
    using namespace std;
    int fac[10] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};//阶乘
    int ans[10];
    int cantor(int a[], int n) {
        int s = 0;
        for(int i = 0; i < n; i++) {
    	int num = 0;
    	for(int j = i + 1; j < n; j++)	
           	    if(a[i] > a[j])
    		num++;
    	s += (num * fac[n - i - 1]);
        }
        return s;
    }
    void decantor(int s, int n) {
        vector<int> a;
        for(int i = 1; i <= n; i++)	a.push_back(i);
        for(int i = 4; i >= 0; i--) {
    	int pos = s / fac[i];
    	s %= fac[i];
    	ans[n - i - 1] = a[pos];
    	a.erase(a.begin() + pos); 
        }
    }
    int main() {
        int a[5] = {4, 5, 3, 1, 2}, n = 5;
        printf("%d
    ", cantor(a, n));
        decantor(cantor(a, n), n);
        for(int i = 0; i < n; i++)
    	printf("%d%c", ans[i], i + 1 == n ? '
    ' : ' ');
        return 0;
    }
    

    树状数组优化代码。

    本来写了一个线段树的,发现好长啊,就没抄上来,改成了树状数组。当然了数据量小,离散化自然也就可以不用了。
    一开始想成了,可以用并查集维护逆康托展开,发现错了,,,想了半天也没想出能用什么比较简洁的数据结构来实现O(n)的逆康托展开。
    这个 (O(n^n)) 的vector成本确实有点高了。

    /*
        Code by lifehappy 2020:04:22
    */
    #include<bits/stdc++.h>
    using namespace std;
    const int N = 15;
    int fac[N] = {1}, ans[N], tree[N], sum, n;
    inline int lowbit(int x) {
        return x & (-x);
    }
    void add(int pos) {
        while(pos <= n) {
            tree[pos]++;
            pos += lowbit(pos);
        }
    }
    int query(int pos) {
        int sum = 0;
        while(pos) {
            sum += tree[pos];
            pos -= lowbit(pos);
        }
        return sum;
    }
    int cantor(int a[], int n) {
        int s = 0;
        memset(tree, 0, sizeof tree);
        for(int i = 0; i < n; i++) {
            int x = a[i] - query(a[i]) - 1;
            add(a[i]);
            s += fac[n - i - 1] * x;
        }
        return s;
    }
    
    void decantor(int s, int n) {
        vector<int> a;
        int cnt = 0;
        for(int i = 1; i <= n; i++)	a.push_back(i);
        for(int i = n - 1; i >= 0; i--) {
            int pos = s / fac[i];
    	s %= fac[i];
            ans[cnt++] = a[pos];
            a.erase(a.begin() + pos); 
        }
    }
    int main() {
        for(int i = 1; i < 15; i++) fac[i] = fac[i - 1] * i;
        n = 4;
        for(int i = 0; i < fac[n]; i++) {
    	decantor(i, n);
            printf("%d
    ", cantor(ans, n));
    	for(int i = 0; i < n; i++)
    	printf("%d ", ans[i]);
            puts("");
            puts("");
        }
        // int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        // do {
            // for(int i = 0; i < 10; i++)
            //     printf("%d ", a[i]);
            // puts("");
        // }while(next_permutation(a, a + n));
    	return 0;
    }
    
  • 相关阅读:
    【转载】实用VC++6.0插件
    关于无标题栏窗口拖动的问题
    VC6配置CXimage库
    扎实基础深入篇(七):函数和类没那么复杂
    扎实基础深入篇(六):while循环带动生产力
    扎实基础深入篇(五):字典也就是个弟弟
    扎实基础深入篇(四):听说if语句很叼?
    lxml类库的xpath的使用
    json与re的再次复习
    python基础汇总(四)
  • 原文地址:https://www.cnblogs.com/lifehappy/p/12748712.html
Copyright © 2020-2023  润新知