1. 程式人生 > 其它 >演算法筆記捏1:康託展開/逆康託展開

演算法筆記捏1:康託展開/逆康託展開

定義

(逆)康託展開構建了一個在 正整數排列正整數 之間的雙射關係,即用一個正整數表示出該正整數序列按照字典序在所有排列中的排名(康託展開),根據排名反推出該排列(逆康託展開)。

康託展開

舉一個例項來說明:

排列P:2 3 5 4 1

排名:36

我們來嘗試構造出所有字典序小於P的排列。

首先,在第 1 位如果放置 1 ,得到的 1*4! 個排列按照字典序一定小於P。

否則,在第 1 位放置 2 (如果放置 3/4/5 顯然不成立),考慮在第二位放置 1( 2 已經在第一位,而 3/4/5 顯然不成立),所得到的 1*3! 個排列一定小於P。

否則,在第 2 位放置 3 ,考慮在第 3 位放置 1/4 ,所得到的 2*2!個排列一定小於P。

否則,在第 3 位放置 5 ,考慮在第 4 位放置 1 ,所得到的 1*1!個排列一定小於P。

否則,得到的是原排列,對小於P的排列無貢獻。

得到小於P的排列個數 4! + 3! + 2*2! + 1! = 35

則P的排名為 35 + 1 = 36

關於程式碼實現,該位置的可選擇的數的個數取決於 該位置之後 小於 原排列中該數的數的個數(前面的不動,也就是用掉了)

也就是說我們從當前位置的下一個開始遍歷,遇到比當前位置更小的就x++,最後加上x*(n-i)!

fac[]為預處理的階乘陣列

上程式碼:

點選檢視程式碼

int cantor(int permutation[], int len)
{
    int rk = 0;
    for (int i = 0; i < len; i++)
    {
        int x = 0;
        for (int j = i + 1; j < len; j++)
            if (permutation[i] > permutation[j])
                x++;
        rk += x * fac[len - i - 1];
    }
    return rk + 1;
}

逆康託展開

同康託展開的想法,每次從rank中去掉儘量多的 (n-i)! ,若rank中有 x 個 (n-i)! ,該位選擇剩下可選數中第 x+1 大的數

關於程式碼實現,直接進行模擬,vector<int> vec 維護剩下的數(這種問題一般規模很小,複雜度什麼的都是浮雲 :P)

上程式碼:

點選檢視程式碼

vector<int> incantor(int rk, int len)
{
    rk--;
    int x;
    vector<int> vec, ans;
    for (int i = 1; i <= len; i++)
        vec.push_back(i);
    for (int i = 1; i <= len; i++)
    {
        ans.push_back(vec[x = rk / fac[len - i]]);
        vec.erase(vec.begin() + x);
        rk %= fac[len - i];
    }
    return ans;
}

小結

下面是總程式碼:

點選檢視程式碼

#include <bits/stdc++.h>
using namespace std;
int fac[20]{1, 1}, num[20];
int cantor(int permutation[], int len)
{
    int rk = 0;
    for (int i = 0; i < len; i++)
    {
        int x = 0;
        for (int j = i + 1; j < len; j++)
            if (permutation[i] > permutation[j])
                x++;
        rk += x * fac[len - i - 1];
    }
    return rk + 1;
}
vector<int> incantor(int rk, int len)
{
    rk--;
    int x;
    vector<int> vec, ans;
    for (int i = 1; i <= len; i++)
        vec.push_back(i);
    for (int i = 1; i <= len; i++)
    {
        ans.push_back(vec[x = rk / fac[len - i]]);
        vec.erase(vec.begin() + x);
        rk %= fac[len - i];
    }
    return ans;
}
int main()
{
    freopen("in.txt", "r", stdin);
    int n;
    cin >> n;
    for (int i = 0; i < n; i++)
        cin >> num[i];
    for (int i = 2; i <= n; i++)
        fac[i] = fac[i - 1] * i;
    int rk = cantor(num, n);
    cout << "rank:" << rk << endl;
    if (rk != 1)
    {
        vector<int> v = incantor(rk, n);
        cout << "permutation:";
        for (int i = 0; i < n; i++)
            cout << v[i] << (i == n - 1 ? "" : " ");
        cout << endl;
    }
    return 0;
}