康擴充開的原理與實現

小K演算法發表於2021-04-30

1.動物園61節來啦

又到了一年一度的61兒童節,動物園裡充滿了歡聲笑語。不僅有好吃的好喝的,還有各種好玩的活動。當然最重量級的就是小朋友們的節目表演啦。
馬老師也開始緊鑼密鼓的籌備節目。

康擴充開的原理與實現

馬老師平時熟讀《孫子兵法》,深知陣型的重要性,先讓同學們變換一下陣型。
馬老師的博學也派上了用場,迅速下發了指令,滿懷期待的看著同學們。

康擴充開的原理與實現

3秒後,同學們依然保持動能守恆,就是原地不動啦。

康擴充開的原理與實現

原來是小朋友們平時沒好好學習《孫子兵法》,根本聽不懂老師說的啥。

康擴充開的原理與實現

馬老師長嘆一聲,只好放棄。那就做簡單的全排列吧,這個你們肯定學過。

康擴充開的原理與實現

但正式表演的時候同學有很多,如果有20個同學,那馬老師怎麼下發指令呢,總不能說“同學們,變換佇列為123456...20”。
馬老師陷入了沉思。。。

2.問題建模

能否找到一種簡單的方法來指定是哪一種排列呢,比如給每個排列隊形取一個名字,如“蘋果隊形,香蕉隊形...”,這樣也行,不過要想這麼多名字也不容易。
要是能給每個不同的排列按順序編號就完美了。

康擴充開的原理與實現

這樣問題就轉化為:能否找一個編號與排列的一一對映,簡稱雙射。
\(f(編號)=排列,f^{-1}(排列)=編號\)

康擴充開的原理與實現

這就要說到一個著名的數學定理了,康託展開

3.康託展開

康託展開是全排列與自然數的雙射,常用於空間壓縮。
本質是計算當前排列在所有由小到大全排列中的順序,因此可逆。

3.1 排列\(\rightarrow\)自然數

康擴充開的原理與實現

3.2 自然數\(\rightarrow\)排列

康擴充開的原理與實現

4.程式碼實現

4.1 康託編碼

// 0-9的階乘
const int FAC[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};

int cantor(int a[], int n) {
    int ans = 0;
    for (int i = 0; i < n; ++i) {
        int lessThan = 0;
        for (int j = i + 1; j < n; ++j) {
            if (a[j] < a[i]) lessThan++;
        }
        ans += lessThan * FAC[n - i - 1];
    }
    return ans;
}

4.2 康託解碼

void decode(int ans[], int x, int n) {
    bool visit[9] = {false};
    for (int i = 0; i < n; ++i) {
        ans[i] = x / FAC[n - i - 1];
        int j, order = 0;
        for (j = 0; j < n; ++j) {
            if (!visit[j]) {
                if (ans[i] == order) break;
                order++;
            }
        }
        ans[i] = j + 1;
        visit[j] = true;
        x %= FAC[n - i - 1];
    }
}

例題poj1077


掃描下方二維碼關注公眾號:小K演算法,第一時間獲取更新資訊!

康擴充開的原理與實現

相關文章