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算法,第一時間獲取更新信息!
