一. 题目
立体推箱子是一个风靡世界的小游戏。
游戏地图是一个 N 行 M 列的矩阵,每个位置可能是硬地(用 .
表示)、易碎地面(用 E
表示)、禁地(用 #
表示)、起点(用 X
表示)或终点(用 O
表示)。
你的任务是操作一个 1×1×2 的长方体。
这个长方体在地面上有两种放置形式,“立”在地面上(1×1 的面接触地面)或者“躺”在地面上(1×2 的面接触地面)。
在每一步操作中,可以按上下左右四个键之一。
按下按键之后,长方体向对应的方向沿着棱滚动 90 度。
任意时刻,长方体不能有任何部位接触禁地,并且不能立在易碎地面上。
字符 X
标识长方体的起始位置,地图上可能有一个 X
或者两个相邻的 X
。
地图上唯一的一个字符 O
标识目标位置。
求把长方体移动到目标位置(即立在 O
上)所需要的最少步数。
在移动过程中,X
和 O
标识的位置都可以看作是硬地被利用。
输入格式
输入包含多组测试用例。
对于每个测试用例,第一行包括两个整数 N 和 M。
接下来 N 行用来描述地图,每行包括 M 个字符,每个字符表示一块地面的具体状态。
当输入用例 N=0,M=0 时,表示输入终止,且该用例无需考虑。
输出格式
每个用例输出一个整数表示所需的最少步数,如果无解则输出 Impossible
。
每个结果占一行。
数据范围
3≤N,M≤500
输入样例
输出样例
7 7
#######
#..X###
#..##O#
#....E#
#....E#
#.....#
#######
0 0
10
二. 题目分析
本题是迷宫问题的变形, 要找到"最少移动次数", 使用BFS算法, 遍历所有路径.
难点不在BFS的思路, 而在于状态的处理. 在本题中, 箱子有三种状态, 立着放, 竖着放和横着放. 每种状态要移动的方式不同, 所以要区分开来.
鉴于横着放和竖着放时, 有两个方格在地图上, 为了简化解题过程, 我们设置一个"核心", 配合箱子的"状态"我们就能确定方块的位置. 立着放时, 显然箱子"核心"标记在下面这个箱子; 竖着放时, 我们将"核心"标记在上面的方格上; 横着放时, 我们将"核心"标记在左边的方格上.("核心"的标注可以自定义, 但是注意在移动后按照自己的定义标注方式更新核心的位置).
这里我们按照上图从左到右的顺序, 用0, 1, 2三个数字来表示三种状态.
对应三种状态, 我们还需要定义三种状态下向四个方向移动的方式:
//状态0标志立起来, 状态1表示竖着放, 状态2标志横着放
//nextx[i][j]表示在状态i下向j方向移动时坐标x应该如何改变
//nextstat[i][j]表示在状态i下向j方向移动后,箱子的状态
//以下三个状态都按照上,下,左,右的移动顺序
int nextx[3][4] = { {-2,1,0,0},{-1,2,0,0},{-1,1,0,0} }, nexty[3][4] = { {0,0,-2,1},{0,0,-1,1},{0,0,-1,2} };
int nextstat[3][4] = { {1,1,2,2},{0,0,1,1},{2,2,0,0} };
和经典的迷宫问题相同, 我们需要记录一下某个位置是否已经"走过"了. 但是与迷宫问题不同的是, 我们定义一个位置已经"走过"了需要注意两个点, 即核心位置和状态都要相同. 如下, 虽然核心位置相同, 但是两个箱子的状态不相同, 我们也不能定义为已经走过了.
最后一个需要注意的点是, 当箱子的状态为1或2时, 我们要检查箱子的两个方格是否都在合法位置而不仅是"核心".
三. 代码实现
#include<iostream>
#include<cstring>
#include<queue>
#define Max 505
using namespace std;
char map[Max][Max];
bool flag[3][Max][Max]={false};
pair<int, int>start, End;
int estat=0;
int n, m;
//状态0标志立起来, 状态1表示竖着放, 状态2标志横着放
//nextx[i][j]表示在状态i下向j方向移动时坐标x应该如何改变
//nextstat[i][j]表示在状态i下向j方向移动后,箱子的状态
//以下三个状态都按照上,下,左,右的移动顺序
int nextx[3][4] = { {-2,1,0,0},{-1,2,0,0},{-1,1,0,0} }, nexty[3][4] = { {0,0,-2,1},{0,0,-1,1},{0,0,-1,2} };
int nextstat[3][4] = { {1,1,2,2},{0,0,1,1},{2,2,0,0} };
struct S {
int x, y;
int stat;
int step;
};
bool check(struct S t) {
if (t.stat == 0) {
if (map[t.x][t.y] == '#'|| map[t.x][t.y] == 'E')//立起来只能在坚实的地面上
return false;
}
else if (t.stat == 1) {
if (map[t.x][t.y] == '#' || map[t.x + 1][t.y] == '#')//竖着放和横着放要检查核心和另一个方格是否都在合法的地面上
return false;
}
else {
if (map[t.x][t.y] == '#' || map[t.x][t.y + 1] == '#')
return false;
}
return true;
}
int BFS(pair<int, int>pp, int stat) {
S p;
p.x = pp.first, p.y = pp.second, p.stat = stat;
p.step = 0;
queue<S>q;
q.push(p);
int i;
flag[p.stat][p.x][p.y] = true;
while (!q.empty()) {
S t = q.front();
q.pop();
if (t.stat == estat && t.x == End.first && t.y == End.second)
return t.step;
for (i = 0; i < 4; i++) {
S temp;
temp.x = t.x + nextx[t.stat][i];
temp.y = t.y + nexty[t.stat][i];
temp.stat = nextstat[t.stat][i];
temp.step = t.step + 1;
if (!flag[temp.stat][temp.x][temp.y] && check(temp)) {
flag[temp.stat][temp.x][temp.y] = true;
q.push(temp);
}
}
}
return -1;
}
int main() {
int i,j;
while (cin >> n >> m && n != 0 && m != 0) {
for (i = 0; i < n; i++) {
for (j = 0; j < m; j++)
{
cin >> map[i][j];
if (map[i][j] == 'X')
start.first = i, start.second = j;
else if (map[i][j] == 'O')
End.first = i, End.second = j;
}
}
int sstat=0;
int dx[] = { 0,0,1,-1 },dy[]={1,-1,0,0};
for (i = 0; i < 4; i++) {//修改起点和终点的标志位
int sx = start.first + dx[i], sy = start.second+dy[i];
int ex = End.first + dx[i], ey = End.second + dy[i];
if (map[sx][sy] == 'X') {
if (i == 3)//方块是竖的,标志位打在上面的方格
{
start.first -= 1;
sstat = 1;
}
if (i == 1)//方块是横的标志位打在左边的方格
{
start.second -= 1;
sstat = 2;
}
}
if (map[ex][ey] == 'O') {
if (i == 2)//方块是竖的,标志位打在上面的方格
{
End.first -= 1;
estat = 1;
}
if (i == 1)//方块是横的标志位打在左边的方格
{
start.second -= 1;
estat = 2;
}
}
}
memset(flag, false, sizeof(flag));
int t= BFS(start, sstat);
if (t == -1)
cout << "Impossible" << endl;
else
cout << t<<endl;
}
return 0;
}