Tobo 是一個 3×3 的塑膠棋盤,編號 1~9。棋盤上有四個旋鈕(A、B、C、D),每個旋鈕可順/逆時針旋轉 90°,帶動周圍四個格子一起轉動。給定一個經過 X 步打亂的棋盤狀態,要求在不超過 X 步內還原到標準狀態(123456789),求最少步數;若無法在 X 步內完成則輸出 -1。
h = (錯位數 + 3) / 4。這是可接受的(admissible)估價,不會高估實際步數。depth + h > limit 直接返回失敗。limit = h(initial) 開始,逐層加深直到找到解或超過 X 步。// 旋鈕影響的四個位置 (0-based 索引)
const int dial[4][4] = {
{0, 1, 4, 3}, // A
{1, 2, 5, 4}, // B
{3, 4, 7, 6}, // C
{4, 5, 8, 7} // D
};
// 順時針旋轉
void rotate_cw(int d, string &s) {
char t = s[dial[d][0]];
s[dial[d][0]] = s[dial[d][3]];
s[dial[d][3]] = s[dial[d][2]];
s[dial[d][2]] = s[dial[d][1]];
s[dial[d][1]] = t;
}
// 逆時針旋轉
void rotate_ccw(int d, string &s) {
char t = s[dial[d][0]];
s[dial[d][0]] = s[dial[d][1]];
s[dial[d][1]] = s[dial[d][2]];
s[dial[d][2]] = s[dial[d][3]];
s[dial[d][3]] = t;
}
// 啟發函數
int heuristic(const string &s) {
int mis = 0;
for (int i = 0; i < 9; ++i)
if (s[i] != '1' + i) ++mis;
return (mis + 3) / 4;
}
// IDA* 遞歸
bool dfs(int depth, int limit, string &s, int last_dial, int last_dir) {
int h = heuristic(s);
if (h == 0) return true;
if (depth + h > limit) return false;
for (int d = 0; d < 4; ++d) {
for (int dir = 0; dir < 2; ++dir) { // 0: cw, 1: ccw
if (d == last_dial && dir == 1 - last_dir) continue; // 逆操作剪枝
if (dir == 0) rotate_cw(d, s);
else rotate_ccw(d, s);
if (dfs(depth + 1, limit, s, d, dir)) return true;
// 回溯:做相反旋轉還原
if (dir == 0) rotate_ccw(d, s);
else rotate_cw(d, s);
}
}
return false;
}
每組測試資料輸出 k. R(k 從 1 開始),若無解則 R = -1。樣例輸出:1. 2、2. -1。務必注意點號後的空格,避免 PE (Presentation Error)。
本題是 IDA* 經典入門題:狀態空間小(9! = 362,880),啟發函數設計巧妙,配合逆操作剪枝即可在時限內解決。關鍵在於正確建模旋鈕旋轉、設計可接受的 h(n)、以及迭代加深的控制流程。