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