數字華容道的規則是這樣的:給出乙個亂序矩陣,你每次只能將數字0與其相鄰的數字交換位置,通過一系列交換,將矩陣排成有序的形式(見下圖,源自博主編的小遊戲)。
那麼,有沒有一種方法,能夠求出該問題的最優解(最少交換次數)呢?
我們不妨嘗試下最暴力的方法:「搜尋」。由於每次交換消耗的代價相等(均為1步),因此我們可以使用寬度優先搜尋(bfs)。
要確定bfs所需的佇列空間大小,首先得計算在該問題中可能出現多少種「狀態」。
我們不妨將矩陣看成乙個字串,那麼由排列的知識,我們可以顯然地得到其總狀態數為
如此大量的狀態數,如果用普通的列舉判重,時間複雜度為
所以我們應當優化狀態判重的方法,由於所有狀態構成了9個數字全排列的所有情況,我們可以利用康托展開給狀態編號。
康托展開可以將每種狀態壓縮成其唯一對應的數字,從而可以通過完美雜湊判重,其公式是:
int f(int now)
return tot;
}
那麼,在判重時只需要在雜湊表中判斷其對應的狀態是否出現過即可。
分別向上下左右交換達成新的狀態,這個就比較簡單了。
void move_up()
return;
}void move_down()
return;
}void move_left()
return;
}void move_right()
return;
}
目標狀態轉換成字串後其實就是:「1234567890」,通過康托展開可以求得其對應的編碼為46233,那麼每次出現新狀態,直接判斷其是否一致即可。
另外,在寬搜中,首先達到目標狀態的,一定為最優解。因此只要達到目標狀態,便可直接退出程式。
bool check() //判斷是否達到目標狀態
我們可以用乙個陣列last表示佇列中某個狀態是由哪乙個狀態轉移而來,那麼只要從目標狀態回溯一遍,即可求出完整方案。(此處**略)
#include#include#includeusing namespace std;
int head,tail;
int cantor[10];
int step[362881];
int location[362881];
char a[4][4];
char que[362881][10];
bool vis[362881];
int f(int now)
return tot;
}void move_up()
return;
}void move_down()
return;
}void move_left()
return;
}void move_right()
return;
}void first()
bool check() //判斷是否達到目標狀態
void print()
printf("\n");
return;
}int main()
first();
head=1;
tail=1;
vis[f(1)]=true;
if (check())
while (head<=tail)
move_down();
if (check())
move_left();
if (check())
move_right();
if (check())
head++;
} printf("no solution");
return 0;
}
康托展開和康托逆展開解決第K個排列問題
集合包含了 n!種不同的排列,將這n!種排列從小到大進行排序,某個排列為第k 個,求k時,使用康托展開,已知k求對應的排列時,使用康托逆展開。對於某個排列 a1,a2,a3,an 將其存入陣列s 求它在n!個排列中的位置k,用rli表示ai後面比ai小的數的個數,ri表示ai後面的數的個數,k 1 ...
loj 1165 bfs 康托展開)
思路 題目意思很簡單,就是通過一些位置的交換,最後變成有序數列,對於一組序列,我們可以用康托展開然後hash判重。然後就是普通的bfs,稍微留意一下細節即可。1 include2 include3 include4 include5 include6 using namespace std 7int...
HDU 1043 Eight(康托展開 BFS)
八數碼問題,輸入9個字元代表乙個3 3的棋盤,對於能還原的輸入,輸出一段包含 u d l r 操作的操作序列,不能還原的輸入輸出 unsolvable 輸入有多組,所以考慮先預處理出所有情況的操作序列,再根據輸入進行輸出 因為要遍歷所有情況,所以選擇 bfs 即可,從結果出發,每操作一次,儲存一次該...