一 问题描述
你站在一个由方格组成的二维度迷宫中,这些格子可能被填满岩石,也可能没被填满岩石。你可以一步一个格子地往北、往南、往东或往西移动。这样的动作叫作“走”。其中一个空单元格包含一个箱子,你可以站在箱子旁边,推动箱子到相邻的自由单元格。这样的动作叫作“推”。箱子除了用推的方式,不能移动,如果你把它推到角落里,就再也不能把它从角落里拿出来了。将其中一个空单元格标记为目标单元格。你的工作是通过一系列走和推把箱子带到目标格子里。由于箱子很重,所以要尽量减少推动的次数。编写程序,计算最后的移动顺序。
二 输入和输出
1 输入
数据包含多个测试用例。每个测试用例的第 1 行都包含两个整数 r 和 c,表示迷宫的行数和列数。接下来是 r 行,每行 c 个字符,每个字符都描述迷宫中的一个格子,对被填满岩石的格子用“#”表示,对空格用“.”表示,对起始位置用“S”表示,对箱子的起始位置用“B”表示,对目标单元格用“T”表示。输入端以两个 0 终止。
2 输出
对于输入中的每个迷宫,都首先输出迷宫的编号。如果无法将箱子带到目标单元格里,则输出“Impossible”,否则输出一个最小推动次数的序列。如果多个这样的序列,则请选择一个最小总移动(走和推)次数的序列。如果仍然有多个这样的序列,则任务一个都可被接受。将序列输出为由 N、S、E、W、n、s、e 和 w 组成的字符串,大写表示推,小写表示走,祖母分别表示北、南、东和西这 4 个方向。在每个测试用例之后都输出一个空行。
三 输入和输出样例
1 输入样例
1 7
SB....T
1 7
SB..#.T
7 11
###########
#T##......#
#.#.#..####
#....B....#
#.######..#
#.....S...#
###########
8 4
....
.##.
.#..
.#..
.#.B
.##S
....
###T
0 0
2 输出样例
Maze #1
EEEEE
Maze #2
Impossible.
Maze #3
eennwwWWWWeeeeeesswwwwwwwnNN
Maze #4
swwwnnnnnneeesssSSS
四 分析
本问题是推箱子问题,要求先保证推箱子的次数最少,在此基础上再让人走的步数最少。推箱子时,人只有站在箱子反方向的前一个位置,才可以将箱子推向下一个位置。因此在移动箱子时,不仅需要判断新位置有没有岩石,还需要判断人是否可以到达反方向的前一个位置,在两者均有效时,才会让人移动。
先求解箱子到目标位置的最短路径(BFS1),在推箱子的过程中,每推一步,都根据推的方向和箱子的位置得到箱子的前一个位置,再求解人到达这个位置的最短路径(BFS2)。在 BFS1 里面嵌套 BFS2 ,属于嵌套广度优先搜索。
五 算法设计
1 定义一个标识数组 vis[][],并将其初始化为 0,标识所有位置都未被访问。
2 创建一个队列 q 维护箱子的状态,将人的初始位置 (sx,sy)、箱子的初始位置(bx,by)和初始路径(“”)入队,标记箱子的位置 vis[bx][by]=1。
3 如果队列不空,则队头 now 出队,否则返回 false。
4 从箱子的当前位置开始,向北、南、东、西这 4 个方向扩展。
得到箱子的新位置:nbx=now.bx+dir[i][0];nby=now.by+dir[i][1]。
得到箱子的前一个位置:tx=now.bx-dir[i][0];nby=now.by-dir[i][1]。
如果这两个位置有效,则执行 BFS2 搜素人到达箱子的前一个位置(tx,ty)的最短路径,并记录路径 path。如果 BFS2 搜素成功,则判断是否到达目标,如果是,则返回答案 ans=now.path+path+dpath[i];否则标记箱子的新位置被访问 vis[nbx][nby]=1,将人的新位置(now.bx,now.by)、箱子的新位置(nbx,nby)和已走过的路径(now.path+path+dpathB[i]) 入队。
5 转向步骤 3
六 代码
package com.platform.modules.alg.alglib.poj1475;
import java.util.LinkedList;
import java.util.Queue;
public class Poj1475 {
public String output = "";
private int N;
private int M;
char mp[][] = new char[25][25];
// 人和箱子的初始位置
private int sx;
private int sy;
private int bx;
private int by;
private final int dir[][] = {{-1, 0}, {1, 0}, {0, 1}, {0, -1}};
private final char dpathB[] = {'N', 'S', 'E', 'W'};
private final char dpathP[] = {'n', 's', 'e', 'w'};
private String ans;
public String cal(String input) {
String[] line = input.split("\n");
String[] words = line[0].split(" ");
N = Integer.parseInt(words[0]);
M = Integer.parseInt(words[1]);
for (int i = 0; i < N; i++) {
String mapLine = line[i + 1];
for (int j = 0; j < mapLine.length(); j++) {
mp[i][j] = mapLine.charAt(j);
if (mp[i][j] == 'S') {
sx = i;
sy = j;
}
if (mp[i][j] == 'B') {
bx = i;
by = j;
}
}
}
if (bfs())
output += ans;
else
output += "Impossible";
return output;
}
boolean check(int x, int y) {
if (x < 0 || x >= N || y < 0 || y >= M) return false;
if (mp[x][y] == '#') return false;
return true;
}
boolean bfs() {
int vis[][] = new int[25][25];
vis[bx][by] = 1;
Queue<node> q = new LinkedList<>(); // 创建一个普通队列(先进先出)
q.add(new node(sx, sy, bx, by, ""));
while (!q.isEmpty()) {
node now = q.peek();
q.poll();
for (int i = 0; i < 4; i++) {
// 箱子的新位置
int nbx = now.bx + dir[i][0];
int nby = now.by + dir[i][1];
// 箱子的前一个位置,人必须能到达这个位置
int tx = now.bx - dir[i][0];
int ty = now.by - dir[i][1];
String path = "";
StringBuilder pathBuilder = new StringBuilder(path);
if (check(nbx, nby) && check(tx, ty) && vis[nbx][nby] == 0) {
if (bfs2(now.px, now.py, now.bx, now.by, tx, ty, pathBuilder)) {
if (mp[nbx][nby] == 'T') {
ans = now.path + path + dpathB[i];
return true;
}
vis[nbx][nby] = 1;
q.add(new node(now.bx, now.by, nbx, nby, now.path + pathBuilder.toString() + dpathB[i]));
}
}
}
}
return false;
}
boolean bfs2(int ppx, int ppy, int bbx, int bby, int tx, int ty, StringBuilder pathBuilder) {
// 局部标识数组,不要定义全局
int vis[][] = new int[25][25];
vis[ppx][ppy] = 1; // 人的位置
vis[bbx][bby] = 1; // 箱子的位置
Queue<person> Q = new LinkedList<>(); // 创建一个普通队列(先进先出)
Q.add(new person(ppx, ppy, ""));
while (!Q.isEmpty()) {
person now = Q.peek();
Q.poll();
if (now.x == tx && now.y == ty) { // 目标位置,即箱子的前一个位置
pathBuilder.append(now.path);
return true;
}
for (int i = 0; i < 4; i++) {
// 人的新位置
int npx = now.x + dir[i][0];
int npy = now.y + dir[i][1];
if (check(npx, npy) && vis[npx][npy] == 0) {
vis[npx][npy] = 1;
Q.add(new person(npx, npy, now.path + dpathP[i]));
}
}
}
return false;
}
}
class person {
int x;
int y;
String path;
person(int x_, int y_, String path_) {
x = x_;
y = y_;
path = path_;
}
}
class node {
int px, py, bx, by;
String path;
node(int px_, int py_, int bx_, int by_, String path_) {
px = px_;
py = py_;
bx = bx_;
by = by_;
path = path_;
}
}