1. 程式人生 > 實用技巧 >HDU6171 Admiral 題解 折半搜尋

HDU6171 Admiral 題解 折半搜尋

題目連結:http://acm.hdu.edu.cn/showproblem.php?pid=6171

題目大意:

假設你是一個著名的海軍上將。我們海軍有 \(21\) 艘戰艦。戰艦有 \(6\) 種類型。
首先,我們有一艘 指揮艦 ,海軍上將必須在其中,它用數字 \(0\) 表示。其他的戰艦的型別用 \(1\)\(5\) 之間的數字表示,它們各有 \(2\)\(3\)\(4\)\(5\)\(6\) 艘。因此,我們總共有 \(21\) 艘戰艦,我們必須與敵人進行一場殊死的戰鬥。因此,如何正確地安排每種型別的戰艦對我們來說是非常重要的。
戰場的形狀如下圖所示。
為了簡化問題,我們假設所有戰艦都有相同的矩形形狀。

幸運的是,我們已經知道了戰艦的最佳陣型。
正如你所見,陣型由 \(6\) 排組成,其中第 \(i\) 排有 \(i\) 艘戰艦(每一種戰艦在圖中對應不同的顏色),其中第 \(i\) 排對應的是數字 \(i\) 對應的數字的戰艦(注意:本題中行號和列號的座標都是從 \(0\) 開始的,即:陣型的對上面那個位置是第 \(0\) 行的第 \(0\) 個位置)。
您將獲得戰場的初始狀態作為輸入。你可以通過改變指揮艦與相鄰戰艦的位置來改變戰場狀態。
當且僅當兩艘戰艦不在同一排並且共享部分邊緣時,才認為它們相鄰。舉個例子,如果我們用 \((i,j)\) 表示在陣型的第 \(i\) 排第 \(j\) 個位置的戰艦,那麼和 \((i,j)\)

相鄰的戰艦有 \((i-1,j-1),(i-1,j),(i+1,j),(i+1,j+1)\)。比如:和 \((2,1)\) 相鄰的戰艦有 \((1,0),(1,1),(3,1),(3,2)\)
你的任務是使用最少的交換次數來改變戰艦的位置使其達到最佳陣型。

解題思路:

如果 \(20\) 步之內沒有辦法達到目標效果則直接結束。所以考慮可以使用搜索,但是事件負責度達到了 \(O(4^{20}) = O(2^{40})\)

考慮使用折半搜尋,

  • 從初始狀態做一個深度不超過 \(10\) 的 DFS;
  • 從終點狀態做一個深度不超過 \(10\) 的 DFS。

時間複雜度降到 \(O(40 \cdot 2^20)\)

示例程式碼:

#include <bits/stdc++.h>
using namespace std;
int T, a[6][6], ans;
map<long long, int> mp;
int to_ll() {
    long long ans = 0, t = 1;
    for (int i = 0; i < 6; i ++) {
        for (int j = 0; j <= i; j ++) {
            ans += a[i][j] * t;
            t *= 6;
        }
    }
    return ans;
}
int dir[4][2] = { -1, -1, -1, 0, 1, 0, 1, 1 };
bool in_map(int x, int y) {
    return x >= 0 && x < 6 && y >= 0 && y <= x;
}
bool check() {
    for (int i = 0; i < 6; i ++)
        for (int j = 0; j <= i; j ++)
            if (a[i][j] != i)
                return false;
    return true;
}
void dfs1(int d) {
    if (d > 10 || d > ans) return;
    if (check()) {
        ans = d;
        return;
    }
    int s = to_ll();
    if (mp.find(s) != mp.end()) {
        int dd = mp[s];
        if (dd <= d) return;
    }
    mp[s] = d;
    int x, y;
    for (int i = 0; i < 6; i ++)
        for (int j = 0; j <= i; j ++)
            if (!a[i][j]) x = i, y = j;
    for (int i = 0; i < 4; i ++) {
        int xx = x + dir[i][0], yy = y + dir[i][1];
        if (in_map(xx, yy)) {
            swap(a[x][y], a[xx][yy]);
            dfs1(d+1);
            swap(a[x][y], a[xx][yy]);
        }
    }
}
void dfs2(int d) {
    if (d > 10) return;
    int s = to_ll();
    if (mp.find(s) != mp.end()) {
        ans = min(ans, mp[s] + d);
        return;
    }
    int x, y;
    for (int i = 0; i < 6; i ++)
        for (int j = 0; j <= i; j ++)
            if (!a[i][j]) x = i, y = j;
    for (int i = 0; i < 4; i ++) {
        int xx = x + dir[i][0], yy = y + dir[i][1];
        if (in_map(xx, yy)) {
            swap(a[x][y], a[xx][yy]);
            dfs2(d+1);
            swap(a[x][y], a[xx][yy]);
        }
    }
}
int main() {
    scanf("%d", &T);
    while (T --) {
        ans = 100;
        mp.clear();
        for (int i = 0; i < 6; i ++)
            for (int j = 0; j <= i; j ++)
                scanf("%d", &a[i][j]);
        dfs1(0);
        if (ans < 100) {    // 如果在10步之內能搞定,就不用第二個DFS了
            printf("%d\n", ans);
        }
        else {  // 否則,從終點做DFS
            // 先將初始狀態設定為終點的狀態
            for (int i = 0; i < 6; i ++) for (int j = 0; j <= i; j ++) a[i][j] = i;
            dfs2(0);
            if (ans < 100) printf("%d\n", ans);
            else puts("too difficult");
        }
    }
    return 0;
}