题目描述
……机器人的形状是一个直径米的球……储藏室是一个的网格,有些格子为不可移动的障碍。机器人的中心总是在格点上……
输入格式
……分别为起始点和目标点左上角网格的行与列……终点的面向方向是任意的。
参考代码
const int MAXN = 100;
struct node
{
int x,y,asp,Step;
node(void) : x(0),y(0),asp(0),Step(0) {}
node(int _x,int _y,int _asp,int _Step) : x(_x),y(_y),asp(_asp),Step(_Step) {}
}S,T,cur;
char asp;
int n,m,ans = INT_MAX;
int base[MAXN][MAXN],Map[MAXN][MAXN],vis[MAXN][MAXN][5];
int turn[5][4] = {{},{0,4,2},{0,1,3},{0,2,4},{0,3,1}};
queue<node> Q;
void bfs()
{
Q.push(S);
if (Map[S.x][S.y] == 1) return;//特判:起点处有障碍的情况。
while (!Q.empty())
{
cur = Q.front();
Q.pop();
if (cur.x == T.x && cur.y == T.y)
{
ans = min(ans,cur.Step);
continue;
}//判断是否到终点。
for (int i = 1;i <= 5;++i)
{
int nx = cur.x;
int ny = cur.y;
int na = cur.asp;
if (i <= 3)
{
if (na == 1) nx -= i;
else if (na == 2) ny += i;
else if (na == 3) nx += i;
else ny -= i;
}
else na = turn[na][i - 3];
if (nx < 1 || ny < 1 || nx > n - 1 || ny > m - 1 || vis[nx][ny][na]) continue;
if (Map[nx][ny] == 1 && (i >= 1 && i <= 3))
{
i = 3;
continue;
}
Q.push(node(nx,ny,na,cur.Step + 1));
vis[nx][ny][na] = true;//访问标记。
}
}
}
void init()
{
scanf("%lld%lld",&n,&m);
for (int i = 0;i < n;++i)
for (int j = 0;j < m;++j)
scanf("%lld",&base[i][j]);
for (int i = 0;i < n;++i)
for (int j = 0;j < m;++j)
if (base[i][j]) Map[i][j] = Map[i + 1][j] = Map[i][j + 1] = Map[i + 1][j + 1] = 1;
scanf("%lld%lld%lld%lld %c",&S.x,&S.y,&T.x,&T.y,&asp);
if (asp == 'N') S.asp = 1;
else if (asp == 'E') S.asp = 2;
else if (asp == 'S') S.asp = 3;
else S.asp = 4;
}
void work()
{
bfs();
if (ans == INT_MAX) printf("-1");
else printf("%lld",ans);
}
分析
- BFS细节题。
(图片来源:https://www.luogu.org/blog/lrt/solution-p1126)
本题有题干中有一些注意点,在上面已经标注。那么,对于测试数据,按照我们一般的思维,地图应该就是像上图那样,蓝框内的绿色点可以走,橙色点不能。
为此,在读入数据的时候,先用base[][]数组保存每个障碍的左上角顶点的坐标,然后再实际的情况写入Map[][]数组:
for (int i = 0;i < n;++i)
for (int j = 0;j < m;++j)
scanf("%lld",&base[i][j]);
for (int i = 0;i < n;++i)
for (int j = 0;j < m;++j)
if (base[i][j]) Map[i][j] = Map[i + 1][j] = Map[i][j + 1] = Map[i + 1][j + 1] = 1;
另外需要注意的是,机器人只能向左转或是向右转。作者一开始以为机器人是可以一下子转到背后的
设北、东、南、西分别为1、2、3、4,对于每个朝向,预处理它左右转可以面朝哪个方向:
int turn[5][4] = {{},{0,4,2},{0,1,3},{0,2,4},{0,3,1}};
然后就可以BFS了。作者在写BFS时,遇到了一个问题:假如现在机器人的坐标是(绿色),方向朝北,如果走一步,显然会碰到障碍,但是如果直接枚举走的步数的话,好像又可以“飞”过去,这显然不对了。怎么办呢?
显然,如果走一步都会碰到障碍,那么走2步、3步都是不行的,因此我们可以直接开始枚举转向:
if (Map[nx][ny] == 1 && (i >= 1 && i <= 3))
{
i = 3;
continue;
}
最后为了避免重复扩展,对于每个坐标的每个方向,都使用一个数组记录下来,这样若后面再次枚举到这个状态,直接跳过即可:
vis[nx][ny][na] = true;