OpenJudge-1998:寻找Nemo

1998:寻找Nemo


(详见OpenJudge 1998:寻找Nemo
Description
Nemo 是个顽皮的小孩. 一天他一个人跑到深海里去玩. 可是他迷路了. 于是他向父亲 Marlin 发送了求救信号.通过查找地图 Marlin 发现那片海像一个有着墙和门的迷宫.所有的墙都是平行于 X 轴或 Y 轴的. 墙的厚度可以忽略不计.所有的门都开在墙上并且长度为1. Marlin 只能穿过有门的墙. 因为穿过墙是有危险的 (门旁可能会藏有巨毒的水母), Marlin 想穿过尽量少的门找到 Nemo.
该图显示了一个迷宫的样例及 Marlin 找到 Nemo的路线.

我们假设 Marlin 的初始位置在 (0, 0). 给定 Nemo 的位置和墙及门的位置情况,请你写一个程序计算 Marlin 要找到 Nemo最少要穿过多少道门.

Input
输入有多组测试数据. 每组测试数据以两个非零整数 M 和 N 开始. M 表示迷宫中墙的数目, N 表示门的数目. 接下来有 M 行, 每行包含四个整数描述一堵墙,其格式如下:

x y d t 

(x, y) 表示墙的左下角, d 是墙的方向 – 0 表示它与 X-轴平行, 1 表示它与 Y-轴平行, t 表示墙的长度. 墙的两个顶点坐标在[1,199].
接下来有 N 行,用来描述门的情况:

x y d 

x, y, d 与门的描述含义相同. 因为门的长度是 1, t 被省略了.
每组测试数据的最后一行包含两个正的浮点数:

f1 f2 

(f1, f2) 给出了 Nemo 的位置. 它不在墙和门上.
Output
对于每组测试数据,输出一行,该行包含 Marlin 找到 Nemo需要穿过的最少的门数. 如果他不可能找到 Nemo, 输出 -1.

Sample Input

8 9
1 1 1 3
2 1 1 3
3 1 1 3
4 1 1 3
1 1 0 3
1 2 0 3
1 3 0 3
1 4 0 3
2 1 1
2 2 1
2 3 1
3 1 1
3 2 1
3 3 1
1 2 0
3 3 0
4 3 1
1.5 1.5
4 0
1 1 0 1
1 1 1 1
2 1 1 1
1 2 0 1
1.5 1.7
-1 -1

Sample Output

5
-1

题目解析
这道题是一道极其有趣的搜索题。虽然看上去是求最短路径,用广度优先搜索做,但是也可以用深度优先搜索做!
让我们先来处理输入,这道题是以线段形式输入,但是我们熟悉的数组是以块呈现的,所以我们可以在一个块(元素)里设置墙(四周),即用结构体矩阵,结构体MAZE内有一个数组Around[4]表示该位置四周的布局(墙、门或空)
我们还要定义矩阵的呈现形式——题目的图给出的是第一象限的图像,但是数组是第四象限,且题目是以y轴为行、x轴为列([y][x]),所以我们要将原图顺时针旋转90度。此时的矩阵才是以数组形式呈现的。
然后我们开始设立墙和门。我们可以观察一下——举个样例:

1 1 1 3

我们能够发现它存在于数组中[1][1~3]和[0][1~3]的中间(上和下),于是我们知道:一条输入线段需要在结构体数组内操作2次。又由于输入的坐标(1,1)是矩阵位置(1,1)的左上角,我们可以推断输入的坐标在数组坐标块的左上角。再来判断方向。题目中 1 表示与y轴平行,但与平面直角坐标系的y轴不一样,还记得我们把原图顺时针旋转了90度吗?因此此时的y轴是横放的!也就是说—— 1 表示该线段是横放的;与其对应,0 就表示该线段是竖放的。如何处理2次操作呢?这个需要把横竖分开来讲。横放的情况——我们不难发现输入坐标(i,j)需要处理矩阵坐标点(i,j)的上方和(i-1,j)的下方,此时并不用判断 i-1≥0 ,因为题目限制:墙的坐标为1~199。竖放的情况——我们也不难发现输入坐标(i,j)需要处理矩阵坐标点(i,j)的左方和(i,j-1)的右方,同样,这时也不用判断 j-1≥0。然后门大家可以当做长度为1的墙处理。
此时墙和门都处理完了,还有一组坐标,但它比较特殊——它是浮点数。数组的坐标肯定不能为浮点数啊,于是我们需要把它取整。其实特别简单,我们画出一个表,就能发现不论是x轴坐标还是y轴坐标,只需要直接将浮点数取整就行了

接下来就是寻找了。这里先说作者本身的想法——广度优先搜索。这里取起点很特别,我是从Nemo的位置走到(0,0),也就是说Nemo是起点,而不是(0,0)。同样是四个方向,同样是迷宫,不同在这道题并不是找最短路,而是找最少穿过门。尽管十分类似,我们也需要特殊处理——我们最熟悉的是改变之前处理重复的数组。在之前的讲解中,我们用到了2维bool数组或3维bool数组,但这次,我们用到的是2维int数组。取名为walked。那么作者有必要解释一下walked[i][j]的含义,即表示从Nemo原位置到达坐标(i,j)最少需要穿过的门数量。由于是门的数量,我们不能到达坐标(0,0)后就退出搜索,而是要走到无法继续走了为止,这样能保证walked的每个元素都是最小值。搜索过程中若遇到walked(i,j)比当前到达(i,j)的穿过门的数量小或者相等的话则直接寻找下一个方向。但是题目并没有给出边界,仅是给出了墙出现的范围,遍历200×200个格子的时间复杂度显然太大了,容易超时,这时我们可以人为设置边界。在输入时找到迷宫在x,y轴出现的最远的位置,加1(在边缘留一条路)并储存到r,c中表示边界。还有一种Nemo出现的位置比迷宫出现的位置要远的情况(注意,这种情况并不是没有可能!),则r,c是Nemo的x,y轴加1。最后输出walked(0,0),即从Nemo位置到达(0,0)最少穿过的门数量。

然后再讲一下深度优先搜索,这是我和其他编程社成员讨论的结果。这道题能用深度优先搜索做的根本原因是它并不是求最短路!同样要用到walked,也要用到r,c。我们仅仅是将广度优先搜索换了一个形式——用函数表示。不过我们可以换一个输出方式,即用一个全局变量ans储存从Nemo到(0,0)最少穿过的门数量。搜索中每次到达(0,0)就更新一次最小值。最后输出ans,当然,我们也可以输出walked(0,0)。

先别急着提交,Runtime Error了别怪我。这道题的的数据有BUG!其原因是题目只说了墙的范围,而没有说Nemo的范围,也并没有说Nemo一定在迷宫里面!也就是说Nemo可以在任何位置…比如说(-3,210)。这就大坑了,但也有处理方法,此时的Nemo一定在迷宫外,那么一定可以不穿过1道门就到达(0,0),即答案是0!只要在输入的时候特殊处理就行了。


题外话
这道题是4天前老师叫我们做的,之前我看到这道题感觉十分复杂,就没有做,无情地把Nemo抛弃在了海里。现在做的时候,Nemo又自己出来了(因为是从Nemo的位置走到(0,0)),那为什么还要去救呢?题做完了吐槽一句。


程序样例
自己写的广度优先搜索(打字真累):

/*Lucky_Glass*/
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
//[0]: up [1]:down [2]:left [3]:right
//1:wall 2:door 3:void
struct MAZE{int Around[4];} maze[220][220],Clear;
struct CheakPoint{int x,y,tot;} Cheak;
int main()
{
    memset(Clear.Around,0,sizeof(Clear.Around));
    while(true)
    {
        for(int i=0;i<220;i++)
            for(int j=0;j<220;j++)
                maze[i][j]=Clear;
        int Wall,Door,F[4][2]={{-1,0},{1,0},{0,-1},{0,1}},walked[220][220]={},r=-1e8,c=-1e8;
        scanf("%d%d",&Wall,&Door);
        if(Wall==-1 && Door==-1) break;
        for(int i=0;i<Wall;i++) //Set The Wall
        {
            int x,y,Long,f;
            scanf("%d%d%d%d",&x,&y,&f,&Long);
            for(int j=0;j<Long;j++)
                if(f)
                {
                    maze[x][y+j].Around[0]=1;
                    if(x-1>=0) maze[x-1][y+j].Around[1]=1;
                    c=max(c,y+j+2);
                }
                else
                {
                    maze[x+j][y].Around[2]=1;
                    if(y-1>=0) maze[x+j][y-1].Around[3]=1;
                    r=max(x,x+j+2);
                }
        }
        for(int i=0;i<Door;i++) //Set The Door
        {
            int x,y,f;
            scanf("%d%d%d",&x,&y,&f);
            if(f)
            {
                maze[x][y].Around[0]=2;
                if(x-1>=0) maze[x-1][y].Around[1]=2;
                c=max(c,y+2);
            }
            else
            {
                maze[x][y].Around[2]=2;
                if(y-1>=0) maze[x][y-1].Around[3]=2;
                r=max(x,x+2);
            }
        }
        double Nemox,Nemoy;
        scanf("%lf%lf",&Nemox,&Nemoy);
        if(Nemox<0 || Nemox>=200 || Nemoy<0 || Nemoy>=200)
        {
            printf("0\n");
            continue;
        }
        Cheak.x=Cheak.y=Cheak.tot=0; 
        r=max(r,int(Nemox+2));c=max(c,int(Nemoy+2));
        queue<CheakPoint> Point;
        Point.push(Cheak);
        memset(walked,-1,sizeof(walked));
        walked[0][0]=0;
        while(!Point.empty())
        {
            for(int i=0;i<4;i++)
            {
                CheakPoint G=Point.front();
                Cheak=G;
                Cheak.x+=F[i][0];Cheak.y+=F[i][1];
                if(maze[G.x][G.y].Around[i]==2) Cheak.tot++;
                if(Cheak.x<0 || Cheak.x>=r || Cheak.y<0 || Cheak.y>=c || (walked[Cheak.x][Cheak.y]<=Cheak.tot && walked[Cheak.x][Cheak.y]!=-1) || maze[G.x][G.y].Around[i]==1) continue;
                walked[Cheak.x][Cheak.y]=Cheak.tot;
                Point.push(Cheak);
            }
            Point.pop();
        }
        printf("%d\n",walked[(int)Nemox][(int)Nemoy]);
    }
    return 0;
}

这才是团队合作!深度优先搜索

/*Lucky_Glass*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
//[0]: up [1]:down [2]:left [3]:right
//1:wall 2:door 0:void
int ans,F[4][2]={{-1,0},{1,0},{0,-1},{0,1}},walked[220][220],r,c;
double Nemox,Nemoy;
struct MAZE{int Around[4];} maze[220][220],Clear;
void flag(int x,int y,int tot)
{
    if(x==(int)Nemox && y==(int)Nemoy)
    {
        ans=min(ans,tot);
        return;
    }
    if(walked[x][y]<=tot && walked[x][y]!=-1) return;
    walked[x][y]=tot;
    for(int i=0;i<4;i++)
    {
        int sx=x+F[i][0],sy=y+F[i][1];
        if(sx<0 || sx>=r || sy<0 || sy>=c) continue;
        if(maze[x][y].Around[i]==2) flag(sx,sy,tot+1);
        if(maze[x][y].Around[i]==0) flag(sx,sy,tot);
    }
}
int main()
{
    memset(Clear.Around,0,sizeof(Clear.Around));
    while(true)
    {
        for(int i=0;i<220;i++)
            for(int j=0;j<220;j++)
                maze[i][j]=Clear;
        memset(walked,-1,sizeof(walked));
        r=c=-1e8;
        ans=1e8;
        int Wall,Door;
        scanf("%d%d",&Wall,&Door);
        if(Wall==-1 && Door==-1) break;
        for(int i=0;i<Wall;i++) //Set The Wall
        {
            int x,y,Long,f;
            scanf("%d%d%d%d",&x,&y,&f,&Long);
            for(int j=0;j<Long;j++)
                if(f)
                {
                    maze[x][y+j].Around[0]=1;
                    if(x-1>=0) maze[x-1][y+j].Around[1]=1;
                    c=max(c,y+j+8);
                }
                else
                {
                    maze[x+j][y].Around[2]=1;
                    if(y-1>=0) maze[x+j][y-1].Around[3]=1;
                    r=max(x,x+j+8);
                }
        }
        for(int i=0;i<Door;i++) //Set The Door
        {
            int x,y,f;
            scanf("%d%d%d",&x,&y,&f);
            if(f)
            {
                maze[x][y].Around[0]=2;
                if(x-1>=0) maze[x-1][y].Around[1]=2;
                c=max(c,y+8);
            }
            else
            {
                maze[x][y].Around[2]=2;
                if(y-1>=0) maze[x][y-1].Around[3]=2;
                r=max(x,x+8);
            }
        }
        scanf("%lf%lf",&Nemox,&Nemoy);
        if(Nemox<0 || Nemox>=200 || Nemoy<0 || Nemoy>=200)
        {
            printf("0\n");
            continue;
        }
        r=max(r,(int)Nemox+8);c=max(c,(int)Nemoy+8);
        flag(0,0,0);
        printf("%d\n",ans==1e8? -1:ans);
    }
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值