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;
}