
POJ 3523 The Morning after Halloween
万圣节惊魂夜:万圣节过后,鬼屋里的幽灵们到处乱窜!给定它们的位置和地图,按照如下规则移动:
①每个鬼都不重合
②一回合任意两只鬼不能交换位置
求移动到指定位置所需最少移动次数。
4.5开动脑筋智慧搜索
A*与IDA*
一只鬼很好说,最多三只鬼,强加的规则让最优解扑朔迷离。搜索的时候每个状态必须同时包含三只鬼的位置。由于地图很小,坐标系极值在16以内,所以只需4位就能表示x或y。8位表示一个鬼,24位表示三个鬼,一个整型就能放得下整个状态了。
全部状态有2563个,盲目搜索会超时。若不考虑规则的话,分别对3个鬼执行一次BFS就能拿到距离的下界h*(v)。有了下界,就特别适合使用A*了。

#include <iostream>
#include <queue>
using namespace std;
const int dx[] = {0, -1, 1, 0, 0}, dy[] = {0, 0, 0, -1, 1};
/**
* N个鬼作为整体到目标状态的距离下界
* @param s 当前状态
* @param d_min 下界数组
* @param N 鬼的数量
* @return
*/
inline int h(const int &s, const unsigned short d_min[3][256], const int &N)
{
unsigned short l = 0;
for (int i = 0; i < N; i++)
{
l = max(l, d_min[i][(s >> (i << 3)) & 0xff]);
}
return l;
}
int move_next(int N, char grid[16][20], int s, int direction[3])
{
int next = 0;
for (int i = 0; i < N; i++)
{
const int from = (s >> (i << 3)) & 0xff;
const int to_x = (from >> 4) + dx[direction[i]];
const int to_y = (from & 0xf) + dy[direction[i]];
if (grid[to_x][to_y] == '#')
{
return -i - 1; // 撞墙
}
const int current = (to_x << 4) | to_y;
for (int j = 0; j < i; j++)
{
const int other = (next >> (j << 3)) & 0xff;
if (current == other)
{
return 0; // 重合
}
if (current == ((s >> (j << 3)) & 0xff) && other == from)
{
return 0; // 交换
}
}
next |= current << (i << 3);
}
return next;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
#endif
int W, H, N;
while (scanf("%d%d%d ", &W, &H, &N), N)
{
int init = 0, goal = 0;
char grid[16][20];
for (int i = 0; i < H; i++)
{
scanf("%[^\n]%*c", grid[i]);
for (int j = 0; j < W; j++)
{
if (islower(grid[i][j]))
{
init |= (i << 4 | j) << ((grid[i][j] - 'a') << 3);
grid[i][j] = ' ';
}
else if (isupper(grid[i][j]))
{
goal |= (i << 4 | j) << ((grid[i][j] - 'A') << 3);
grid[i][j] = ' ';
}
}
}
const int A = N >= 0 ? 5 : 1; // 每个鬼能做的动作
const int B = N >= 1 ? 5 : 1;
const int C = N >= 2 ? 5 : 1;
static unsigned short int d_min[3][256]; // d_min[i][j]=n : 第i个鬼的终点离j的距离下界
for (int i = 0; i < N; i++)
{
memset(d_min[i], 0xff, 256 * sizeof(unsigned short int));
queue<int> q;
int position = (goal >> (i << 3)) & 0xff;
q.push(position);
d_min[i][position] = 0;
while (!q.empty())
{
const int s = q.front();
const int x = s >> 4;
const int y = s & 0xf;
q.pop();
for (int d = 1; d < 5; d++)
{
const int to_x = x + dx[d];
const int to_y = y + dy[d];
const int to = (to_x << 4) | to_y;
if (grid[to_x][to_y] != '#' && d_min[i][s] + 1 < d_min[i][to])
{
d_min[i][to] = d_min[i][s] + 1;
q.push(to);
}
}
}
}
static unsigned short dist[256 * 256 * 256];
memset(dist, 0xff, sizeof(dist));
dist[init] = 0;
priority_queue<pair<short, int>, vector<pair<short, int>>, greater<pair<short, int> > > q;
q.push(make_pair(h(init, d_min, N), init));
while (!q.empty())
{
const int cost = q.top().first;
const int s = q.top().second;
q.pop();
if (s == goal)
{
printf("%d\n", cost);
break;
}
const int d = cost - h(s, d_min, N); // 优先队列中存的是cost+h
if (d > dist[s])
{
continue;
}
int direction[3]; // 三个鬼的移动方向
for (direction[0] = 0; direction[0] < A; direction[0]++)
{
for (direction[1] = 0; direction[1] < B; direction[1]++)
{
for (direction[2] = 0; direction[2] < C; direction[2]++)
{
const int next = move_next(N, grid, s, direction);
if (next == 0)
{
continue; // 违反移动规则,剪枝
}
else if (next < 0)
{
if (next == -1)
{
goto SKIP_A; // 第一只鬼无法移动,剪枝
}
else if (next == -2)
{
goto SKIP_B; // 第二个鬼无法移动,剪枝
}
else
{
continue; // 第三只鬼无法移动,剪枝
}
}
if (d + 1 < dist[next])
{
dist[next] = d + 1;
q.push(make_pair(dist[next] + h(next, d_min, N), next));
}
}
SKIP_B:;
}
SKIP_A:;
}
}
}
#ifndef ONLINE_JUDGE
fclose(stdin);
#endif
return 0;
}
| 16516373 | hankcs | 3523 | Accepted | 34540K | 3594MS | C++ | 5370B | 2017-01-24 13:35:05 |
Reference
https://github.com/eagletmt/contests/blob/244c1308ce4ea026626de1f7b08f36069fb45be7/source/2011-10-19-poj-3523.html.liquid.md
知识共享署名-非商业性使用-相同方式共享:码农场 » POJ 3523 The Morning after Halloween 题解《挑战程序设计竞赛》
码农场