#88. 噩梦

文章讲述了如何使用双向广度优先搜索解决一个关于地图的编程问题,其中地图包含一个男孩、一个女孩和两个鬼。男孩和女孩以不同速度移动,鬼会扩张其占领区域。目标是找出男孩和女孩在不被鬼抓住的情况下最快会合的时间。文章提供了代码实现和关键函数解释。
摘要由CSDN通过智能技术生成

题目描述

给定一张N×M的地图,地图中有1个男孩,1个女孩和2个鬼。

字符“.”表示道路,字符“X”表示墙,字符“M”表示男孩的位置,字符“G”表示女孩的位置,字符“Z”表示鬼的位置。

男孩每秒可以移动3个单位距离,女孩每秒可以移动1个单位距离,男孩和女孩只能朝上下左右四个方向移动。

每个鬼占据的区域每秒可以向四周扩张2个单位距离,并且无视墙的阻挡,也就是在第k秒后所有与鬼的曼哈顿距离不超过2k的位置都会被鬼占领。

输入格式

第一行包含整数T,表示共有T组测试用例。

每组测试用例第一行包含两个整数NM,表示地图的尺寸。

接下来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来存男孩和女孩的移动坐标。

  • 然后就到双向广搜时间啦(*^▽^*),步骤如下:

  1. 遍历完mm内所有的位置,其中要用cheak函数(cheak函数下面会说)判断是否可行。如果遇到女孩直接输出。

  1. 遍历完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)就讲完啦,是不是很简单哩!

点个赞吧!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值