题目描述
给定一张N×M的地图,地图中有1个男孩,1个女孩和2个鬼。
字符“.”表示道路,字符“X”表示墙,字符“M”表示男孩的位置,字符“G”表示女孩的位置,字符“Z”表示鬼的位置。
男孩每秒可以移动3个单位距离,女孩每秒可以移动1个单位距离,男孩和女孩只能朝上下左右四个方向移动。
每个鬼占据的区域每秒可以向四周扩张2个单位距离,并且无视墙的阻挡,也就是在第k秒后所有与鬼的曼哈顿距离不超过2k的位置都会被鬼占领。
输入格式
第一行包含整数T,表示共有T组测试用例。
每组测试用例第一行包含两个整数N和M,表示地图的尺寸。
接下来N行每行M个字符,用来描绘整张地图的状况。(注意:地图中一定有且仅有1个男孩,1个女孩和2个鬼)
输出格式
每个测试用例输出一个整数S,表示最短会合时间。
如果无法会合则输出−1。
每个结果占一行。
输入样例
3
5 6
XXXXXX
XZ..ZX
XXXXXX
M.G...
......
5 6
XXXXXX
XZZ..X
XXXXXX
M.....
..G...
10 10
..........
..X.......
..M.X...X.
X.........
.X..X.X.X.
.........X
..XX....X.
X....G...X
...ZX.X...
...Z..X..X
输出样例
1
1
-1
提示
数据范围:1<n,m<800
注意: 每一秒鬼会先扩展,扩展完毕后男孩和女孩才可以移动。
求在不进入鬼的占领区的前提下,男孩和女孩能否会合,若能会合,求出最短会合时间。
思路
这是一道典型的双向广度优先搜索题,也就是将男孩与女孩的起点开始双向广搜,相遇则输出答案。那么理解题意后也就不会很难敲出代码了,下面分享一下我的思路吧。
首先输入t次地图,其中用两组变量来存男孩与女孩的位置,接着用一个数组一次存鬼的位置。注意使用结构体,如下:
struct id
{
int x,y;
};
使用两个队列mm,gg来存男孩和女孩的移动坐标。
然后就到双向广搜时间啦(*^▽^*),步骤如下:
遍历完mm内所有的位置,其中要用cheak函数(cheak函数下面会说)判断是否可行。如果遇到女孩直接输出。
遍历完gg内所有的位置,其中也要赢cheak函数来判断。如果遇到男孩直接输出。
判断男孩与女孩有没有相遇,否则输出-1。
那么基本思路将讲完了。
cheak函数实现及原理
题目说过,鬼可以无视墙,一次移动两个,也就是里鬼曼哈顿距离不超过2的地方都会被鬼占领。
那么就好办啦,我们只要看看男孩(或女孩)离鬼的曼哈顿距离是多少就可以啦。
然后呢,我在我的cheak函数里还考虑到碰到墙和越界的问题。
cheak函数代码实现
bool cheak(int x,int y,int t)
{
if(x<0||y<0||x>=n||y>=m||a[x][y]=='X')
return true;
for(int i=0;i<2;i++)
if(abs(x-gui[i].x)+abs(y-gui[i].y)<=t*2)return true;
return false;
}
好啦,大致思路我们都理解了,那么直接上AC代码:
代码实现
#include<bits/stdc++.h>
using namespace std;
const int N=810;
struct id
{
int x,y;
}gui[2];//结构体,和鬼的位置数组
char a[N][N];//地图
int st[N][N],n,m;//标记数组和地图的长宽
int dx[]={-1,0,1,0};//位置偏移数组
int dy[]={0,1,0,-1};//位置偏移数组
bool cheak(int x,int y,int t)
{
if(x<0||y<0||x>=n||y>=m||a[x][y]=='X')//判断越界和墙
return true;
for(int i=0;i<2;i++)//判断是否会被鬼追上
if(abs(x-gui[i].x)+abs(y-gui[i].y)<=t*2)return true;
return false;
}
int bfs()//双向广搜
{
memset(st,0,sizeof st);
queue<id>mm,gg;
int tot=0;
for(int i=0;i<n;i++)//寻找男孩与女孩的位置和鬼的位置
{
for(int j=0;j<m;j++)
{
if(a[i][j]=='M')
{
st[i][j]=2;
mm.push(id{i,j});
}
else if(a[i][j]=='G')
{
st[i][j]=1;
gg.push(id{i,j});
}
else if(a[i][j]=='Z')
gui[tot++]=id{i,j};
}
}
int times=0;
while(!mm.empty()||!gg.empty())
{
times++;
for(int i=0;i<3;i++)
{
for(int j=0,len=mm.size();j<len;j++)//枚举男孩的位置
{
id t=mm.front();
mm.pop();
if(cheak(t.x,t.y,times))continue;
for(int k=0;k<4;k++)
{
int x=dx[k]+t.x;
int y=dy[k]+t.y;
if(cheak(x,y,times))continue;
if(st[x][y]==1)return times;
if(st[x][y]==0)
{
st[x][y]=2;
mm.push(id{x,y});
}
}
}
}
for(int j=0,len=gg.size();j<len;j++)//枚举女孩的位置
{
id t=gg.front();
gg.pop();
if(cheak(t.x,t.y,times))continue;
for(int k=0;k<4;k++)
{
int x=dx[k]+t.x;
int y=dy[k]+t.y;
if(cheak(x,y,times))continue;
if(st[x][y]==2)return times;
if(st[x][y]==0)
{
st[x][y]=1;
gg.push(id{x,y});
}
}
}
}
return -1;
}
int main()
{
int t;
cin>>t;
while(t--)
{
cin>>n>>m;
for(int i=0;i<n;i++)cin>>a[i];//照常输入
cout<<bfs()<<endl;
}
return 0;
}
后记
好啦,双向广搜的模板题——噩梦(我记得好像有的地方叫魔鬼II)就讲完啦,是不是很简单哩!
点个赞吧!