BFS、DFS ——J - Nightmare

J - Nightmare

Ignatius had a nightmare last night. He found himself in a labyrinth
with a time bomb on him. The labyrinth has an exit, Ignatius should
get out of the labyrinth before the bomb explodes. The initial
exploding time of the bomb is set to 6 minutes. To prevent the bomb
from exploding by shake, Ignatius had to move slowly, that is to move
from one area to the nearest area(that is, if Ignatius stands on (x,y)
now, he could only on (x+1,y), (x-1,y), (x,y+1), or (x,y-1) in the
next minute) takes him 1 minute. Some area in the labyrinth contains a
Bomb-Reset-Equipment. They could reset the exploding time to 6
minutes.

Given the layout of the labyrinth and Ignatius’ start position, please
tell Ignatius whether he could get out of the labyrinth, if he could,
output the minimum time that he has to use to find the exit of the
labyrinth, else output -1.

Here are some rules:

  1. We can assume the labyrinth is a 2 array.
  2. Each minute, Ignatius could only get to one of the nearest area, and he should not walk out of the border, of course he could not walk
    on a wall, too.
  3. If Ignatius get to the exit when the exploding time turns to 0, he can’t get out of the labyrinth.
  4. If Ignatius get to the area which contains Bomb-Rest-Equipment when the exploding time turns to 0, he can’t use the equipment to reset the
    bomb.
  5. A Bomb-Reset-Equipment can be used as many times as you wish, if it is needed, Ignatius can get to any areas in the labyrinth as many
    times as you wish.
  6. The time to reset the exploding time can be ignore, in other words, if Ignatius get to an area which contain Bomb-Rest-Equipment, and the
    exploding time is larger than 0, the exploding time would be reset to

Input

The input contains several test cases. The first line of the input is
a single integer T which is the number of test cases. T test cases
follow. Each test case starts with two integers N and M(1<=N,Mm=8)
which indicate the size of the labyrinth. Then N lines follow, each
line contains M integers. The array indicates the layout of the
labyrinth. There are five integers which indicate the different type
of area in the labyrinth: 0: The area is a wall, Ignatius should not
walk on it. 1: The area contains nothing, Ignatius can walk on it.
2: Ignatius’ start position, Ignatius starts his escape from this
position. 3: The exit of the labyrinth, Ignatius’ target position.
4: The area contains a Bomb-Reset-Equipment, Ignatius can delay the
exploding time by walking to these areas.

Output

For each test case, if Ignatius can get out of the labyrinth, you
should output the minimum time he needs, else you should just output
-1. Sample Input

3
3 3
2 1 1
1 1 0
1 1 3
4 8
2 1 1 0 1 1 1 0
1 0 4 1 1 0 4 1
1 0 0 0 0 0 0 1
1 1 1 4 1 1 1 3
5 8
1 2 1 1 1 1 1 4 
1 0 0 0 1 0 0 1 
1 4 1 0 1 1 0 1 
1 0 0 0 0 3 0 1 
1 1 4 1 1 1 1 1 
Sample Output
4
-1
13

bfs思路如下 :

这一题,起始相较于其他迷宫题,最难想的是 到底要不要进行标记?如果标记应该标记那些点?标记的点对该题的要求是否有影响?(这题的主要思路就 处理标记点的问题 直接看代码吧)

题解如下:
//炸弹重置
//dfs 逃出炸弹迷宫
#include<stdio.h>
#include<queue>
using namespace std;
int m,n;
int mov[4][2] = {{1,0},{0,1},{-1,0},{0,-1}};
int map[10][10]; //存地图
int pos[1][2];  //存储人的初始位置
struct Node
{
    int x,y;
    int time;   //剩余时间
    int step;   //所用步数

}st,en;

queue<Node> q;
void bfs()
{
    while(! q.empty())  //每次调用bfs清空 q队列
        q.pop();

    //赋值、压入队列
    st.x = pos[0][0];
    st.y = pos[0][1];
    st.time = 6;
    st.step = 0;
    q.push(st);
    while(! q.empty())
    {
        st = q.front();
        q.pop();
        for(int i=0;i<4;i++)
        {
            en = st;
            en.x += mov[i][0];
            en.y += mov[i][1];
            en.time--;

            if(en.x<0 || en.y<0 || en.x>=m || en.y>=n || !map[en.x][en.y])  //与其他的BFS题不同,这一题不需要,单独定义一个二数组标记某些点是否走过(如果 这样做了 会影响做题)
                continue;
            if(en.time == 0)    //走到下一个点剩余的时间为 0 ,这一步是不可以走
                break;
            if(map[en.x][en.y] == 4)    //更新时间,并对(en.x,en.y)这个点做标记(只有炸弹重置器位于点需要做的标记)
            {                           //其实这一题最难想的部分就是是否要标记,如果标记了会怎么样不标记会怎样,对于这一题 我们应该不单独定义二维数组进行标记,只需要在原图上,对有 时间重置器的点 进行标记就可以了,
                en.time = 6;            //首先先解释 为什么要标记 有时间重置器的点 ,因为要想获得最短逃出时间,有重置器的点应该只能走一次,重复走这点是没有意义的反而 使最短逃出时间增加(当然重复走其他点 也是这样的),所以要标记 有重置器的点
                map[en.x][en.y] = 0;    //到这里我们还是有个疑问,为什么 除重置器以外的点不进行标记???,如果不标记会不会造成 bfs函数一直循环运行,没有出口(终止点)???,首先解释:不标记其它点,如果标记某个点,这个点可能会影响(这个影响是 指标记后的点就不能再走了,而有的情况 是需要 再次经过这个点的) 其它 经过重置器的点的这个方案正常的向下一层蔓延(该题的最后一个样例就出现了该问题)从而影响答案,
            }                           //再解释第二个问题,为什么不标记 这个dfs不会一直运行下去,因为这题有一个 if(en.time == 0) break;终止条件,虽然 地图上的点出 有重置器外的点均没有进行标记,所以有些点会 多 重复进行走几遍,一旦这些点 的剩余时间为零(注意此时也没 重置器为 这些点刷新时间 因为重置器点已经被标记不可再次访问) 这些点就结束不会再往下一层进行蔓延,这样程序就不会一直运行下去
            en.step++;
            if(map[en.x][en.y] == 3)    //判断是否到达 出口
            {
                printf("%d\n",en.step);
                return;
            }
            q.push(en);
        }
    }
    printf("-1\n");
}

int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&m,&n);
        for(int i=0;i<m;i++)
            for(int j=0;j<n;j++)
            {
                scanf("%d",&map[i][j]);
                if(map[i][j] == 2)
                    pos[0][0] = i,pos[0][1] = j;
            }
        bfs();
    }
    return 0;
}

dfs思路 如下:

看到这一题,我们正常的思维就是在用dfs的时候对迷宫进行,进行标记,使访问过的点不能被访问,但这样却会影响 有些点走过了还需要被走,但是如果我们不进行标记,递归又会无限调用自己,为避免这种情况,我们可以开两个数组Step、Time分别存储 目前到某个点最优的 步数和时间,通过 某一种方案 在某到某一点的 步数和时间情况与当前最优进行比较,如果不是最优,就可以直接终止该方案,从而避免无限递归调用问题,最后在所有可行方案中选出最优即可

题解如下:
#include<iostream>
using namespace std;
int m,n;
int mov[4][2] = {{1,0},{0,1},{-1,0},{0,-1}};
int map[15][15];    //存地图
int Time[15][15];   //存地图上某个点被到达的时间,以供与递归调用中产生的一些方案到该点的时间进行比较,从而达到减枝的目的
int Step[15][15];   //存地图上某个点被到达的时的步数,以供与递归调用中产生的的一些方案到该点的步数进行比较,从而达到减枝的目的
int s_x,s_y;        //起始位置
int min_step;       //可行方案中的最小步数
int flag = 0;
struct Node
{
    int x,y;
    int time;
    int step;
};

void dfs(Node node)
{
    if(node.step >= min_step)   //剪枝1.
        return;
    if(node.step >= Step[node.x][node.y] && Time[node.x][node.y] >= node.time)  //剪枝2.通过当前 某种方案 走到该点的步数、走到该点剩余的时间 与 与存在 数组Step、Time的目前最优解 进行比较
        return;                                                                 //如果条件成立 说明该方案 走到该的点的情况不是最优的,所以retunr去除这种情况;如果不成立说明该方案最有,则替换数组Step、Time中的值
    Step[node.x][node.y] = node.step;
    Time[node.x][node.y] = node.time;
    if(map[node.x][node.y] == 3) //递归终止条件
    {
        flag = 1;
        if(min_step > node.step)
            min_step = node.step;
        return;
    }

    Node temp;
    for(int i=0;i<4;i++)
    {
        temp = node;
        temp.x += mov[i][0];
        temp.y += mov[i][1];
        if(temp.x>=0 && temp.y>=0 && temp.x<m && temp.y <n && map[temp.x][temp.y])  //判断这一步是否可以走
        {
            temp.time--;
            if(temp.time == 0)   //剪枝3.
                return;
            temp.step++;
            if(map[temp.x][temp.y] == 4)
                temp.time = 6;   //注意⚠️:这里不要对(temp.x,temp.y)这个点做标记(会出现错误,虽然我也不知道为什么出现错误)

            dfs(temp);
        }
    }
}

int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        flag = 0;
        min_step = 1e9;
        scanf("%d%d",&m,&n);
        for(int i=0;i<m;i++)
            for(int j=0;j<n;j++)
            {
                scanf("%d",&map[i][j]);
                if(map[i][j] == 2)
                    s_x = i,s_y = j;
                Time[i][j] = 0;
                Step[i][j] = 1e9;
            }
        Node tem;
        tem.x = s_x;
        tem.y = s_y;
        tem.time = 6;
        tem.step = 0;
        dfs(tem);
        if(flag == 1)
            cout<<min_step<<endl;
        else
            cout<<"-1\n";
    }

    return 0;
}

发布了73 篇原创文章 · 获赞 100 · 访问量 2716

猜你喜欢

转载自blog.csdn.net/qq_34261446/article/details/103420867