原题点 这里
这是一道非常经典的迷宫问题,同时考察了编程技巧和宽搜。
题中有许多需要注意的地方:
- 一个机器人占据四个格子,给出的起终点坐标都是其中左上角格子的坐标。
- 可能机器人的初始位置的 4 个格子中就有障碍物,因此需要特判起点是否合法。这是最后一组数据的要点。
- 机器人能前进两格的前提是它能前进一格。前进 3 格的前提是它能前进两格,因此不能仅判断终点位置是否有障碍,而是沿途经过的所有位置是否有障碍。
而本题编程上的技巧也有很多:
- 可以通过宏在任何地方创建内联函数,合并操作,简化代码。
- 对于当前 BFS 树的节点表示,除了需要记录行列位置外,还要保存方向和已经操作的步数。
- 可以用 0、1、2、3 来给方向顺时针编码(我把 N 设为 0,E 为 1,S 为 2,W 为 3),这样左转时将当前方向 -1+4 再 mod 4 即可,右转 +1 再 mod 4 即可。加 4 和 mod 4 是为了保证结果仍在 [0,3] 的范围内。
- 实际上对于前进操作,只编写前进一步的函数即可,前进 3 步等价于前进 3 个一步。而且前进一步后会发现机器人仍有两格还在原来的位置,肯定没有障碍,因此仅判断机器人走出的两个新格子是否有障碍即可。
- 对于转向操作,肯定是能够进行的,直接入队即可。而前进操作则可能失败,如走出棋盘或遇到障碍。本人的处理方法是如果新状态不可行则将其前进方向设为 -1,入队时忽略方向为 -1 的节点。
#include <cstdio>
#include <queue>
#include <cstdlib>
#include <cctype>
using namespace std;
const int maxn= 55;
struct Unit{
int val;
} map[maxn][maxn];
// row = 行,col = column = 列。
// ori = origin = 起点
// tar = target = 终点
// dir = direction = 方向
int map_row, map_col, ori_row, ori_col, ori_dir, tar_row, tar_col;
struct Stat{
int row, col, dir, cnt;
};
bool inq[maxn][maxn][4];
// 返回前进一步后的状态,失败时返回值的 dir 为 -1 。
Stat go(Stat s){
if(s.dir==-1) return s; // 已经失败的状态直接结束。
int r1, c1, r2, c2; // 新扩展的两个格子坐标。
#define not_in_map() (r1<1 || r1>map_row || c1<1 || c1>map_col ||\
r2<1 || r2>map_row || c2<1 || c2>map_col)
#define have_stone() (map[r1][c1].val||map[r2][c2].val)
// 开始判断新格子的坐标。
if(s.dir==0){
--s.row;
r1= s.row; c1=s.col; r2= s.row; c2= s.col+1;
}
else if(s.dir==1){
++s.col;
r1= s.row; c1= s.col+1; r2= s.row+1; c2= s.col+1;
}
else if(s.dir==2){
++s.row;
r1= s.row+1; c1= s.col; r2= s.row+1; c2= s.col+1;
}
else{
--s.col;
r1= s.row; c1= s.col; r2= s.row+1; c2= s.col;
}
// 如果新格子有障碍或机器人走出地图,则失败。
if(not_in_map()||have_stone()){ s.dir= -1; }
return s;
}
queue<Stat> q;
void bfs(){
// 宏可以随处定义,非常方便,但不建议在工程中使用。
// 另外给宏的参数最好只有一个变量名,否则容易引起各种奇怪的错误。
#define is_inq(s) inq[s.row][s.col][s.dir]
#define push(s) q.push(s); inq[s.row][s.col][s.dir]=true;
#define _left(s) (Stat){s.row, s.col, (s.dir+3)%4, s.cnt}
#define _right(s) (Stat){s.row, s.col, (s.dir+1)%4, s.cnt}
#define pop() q.front(); q.pop();
Stat ori_stat= {ori_row, ori_col, ori_dir, -1};
// 特判起点是否有障碍,有则直接结束。
if(map[ori_stat.row][ori_stat.col].val
||map[ori_stat.row][ori_stat.col+1].val
||map[ori_stat.row+1][ori_stat.col].val
||map[ori_stat.row+1][ori_stat.col+1].val){
printf("-1\n"); exit(0);
}
push(ori_stat);
while(q.empty()==false){
Stat front= pop(); ++front.cnt;
if(front.row==tar_row && front.col==tar_col){
printf("%d\n", front.cnt); exit(0); // 到达终点直接结束。
}
Stat left= _left(front), right= _right(front);
// 转向的结果只要不重复就入队。
if(!is_inq(left)) push(left);
if(!is_inq(right)) push(right);
Stat forward[3];
// 将 3 步转化为依次走 1 步。
forward[0]= go(front);
forward[1]= go(forward[0]);
forward[2]= go(forward[1]);
for(int i=0; i<3; ++i){
Stat now= forward[i];
// 前进的结果除了判重还要检查是否成功。
if(now.dir==-1 || is_inq(now)) continue;
push(now);
}
}
printf("-1\n");
}
int main(){
scanf("%d%d", &map_row, &map_col);
for(int i=1; i<=map_row; ++i){
for(int j=1; j<=map_col; ++j){
scanf("%d", &map[i][j].val);
}
}
char dir_ch= ' ';
scanf("%d%d%d%d", &ori_row, &ori_col, &tar_row, &tar_col);
// 尽量不要用 scanf 读取单个字符。
while(isspace(dir_ch=getchar()));
// 将方向转化为数字。
if(dir_ch=='N') ori_dir= 0;
else if(dir_ch=='E') ori_dir= 1;
else if(dir_ch=='S') ori_dir= 2;
else ori_dir= 3;
/*
printf("%d %d %d %d %d %d %d\n", map_row, map_col, ori_row, ori_col, ori_dir, tar_row, tar_col);
for(int i=1; i<=map_row; ++i){
for(int j=1; j<=map_col; ++j){
printf("%d ", map[i][j].val);
}
putchar('\n');
}
*/
bfs();
return 0;
}