C++知识点总结(42):广度优先搜索应用

一、 BFS VS. DFS \text{BFS VS. DFS} BFS VS. DFS

BFS \text{BFS} BFS 适用于求源点和目标结点距离近的情况,例如求最少步数
DFS \text{DFS} DFS 适用于求解一个任意符合方案中的一种或者遍历所有情况,例如能否到达、求所有路径数、全排列

二、例题

1. 小蝌蚪找妈妈

1.1 审题

题目描述

小蝌蚪再次与青蛙妈妈失散。按照约定,青蛙妈妈会位于荷塘上的一片孤立的方形荷叶上,小蝌蚪只有完成两个步骤才有可能找到妈妈。你能帮帮小蝌蚪吗?第一个步骤是检查所有荷叶的形状:荷塘可视为一个 n × n n \times n n×n 的方阵,荷塘上有若干方形荷叶,用 "@" 表示,水域用 "." 表示,青蛙妈妈只会站在孤立的方形荷叶上。在荷塘中,如果两个 "@" 在上下或左右相邻的位置上,但分属两片不同的方形荷叶,则这两片方形荷叶相互接触了。如果存在相互接触的方形荷叶,请你告诉小蝌蚪 "Contact."。第二个步骤是统计荷塘中互不接触的方形荷叶的数量,以便找到妈妈的确切位置。如果荷塘里只存在互不接触的荷叶就输出 "There are x leaves.",其中 x x x 表示互不接触的荷叶数量。

输入描述

第一行为一个整数 n n n 1 ≤ n ≤ 1000 1 \le n \le 1000 1n1000),表示荷塘的大小。接下来 n n n 行,每行 n n n 个字符,为 @.。数据保证荷塘里一定有荷叶。

输出描述

一行一个字符串,如果荷塘里只存在互不接触的荷叶就输出 There are x leaves. x x x 表示互不接触的荷叶数量。否则输出 Contact.

样例1

输入

4
@@.@
@@.@
...@
@@@@

输出

Contact.

1.2 思路

典型的泛洪问题。

要先判断荷叶是否相连,其思路即:只要有拐角的地方,那就是相连。总共只有四种情况: [ @ . @ @ ] \begin{bmatrix}@ & . \\@ & @ \\\end{bmatrix} [@@.@] [ @ @ . @ ] \begin{bmatrix}@ & @ \\. & @ \\\end{bmatrix} [@.@@] [ . @ @ @ ] \begin{bmatrix}. & @ \\@ & @ \\\end{bmatrix} [.@@@] [ @ @ @ . ] \begin{bmatrix}@ & @ \\@ & . \\\end{bmatrix} [@@@.]

1.3 参考答案

#include <iostream>
#include <queue>
using namespace std;

struct Node
{
    int x, y;
};

int n, ans;
char a[1005][1005];
int dx[5] = {-1, 0, 0, 1};
int dy[5] = {0, -1, 1, 0};
queue <Node> q;

bool contact(int i, int j)
{
    int cnt = 0;
    if (a[i][j] == '@') cnt++;
    if (a[i][j+1] == '@') cnt++;
    if (a[i+1][j] == '@') cnt++;
    if (a[i+1][j+1] == '@') cnt++;
    return (cnt == 3);
}

void bfs(int x, int y)
{
    a[x][y] = '.';
    q.push({x, y});
    
    while (!q.empty())
    {
        auto [fx, fy] = q.front();
        q.pop();
        
        for (int i = 0; i <= 3; i++)
        {
            int tx = fx+dx[i];
            int ty = fy+dy[i];
            
            if (tx>=1 && tx<=n && ty>=1 && ty<=n && a[tx][ty]=='@')
            {
                a[tx][ty] = '.';
                q.push({tx, ty});
            }
        }
    }
}

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            cin >> a[i][j];
    
    for (int i = 1; i < n; i++)
        for (int j = 1; j < n; j++)
            if (contact(i, j))
            {
                cout << "Contact.";
                return 0;
            }
    
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            if (a[i][j] == '@')
            {
                ans++;
                bfs(i, j);
            }
    
    cout << "There are " << ans << " leaves.";
	return 0;
}

2. 流星雨

2.1 审题

题目描述

贝茜听说一场特别的流星雨即将到来:这些流星会撞向地球,并摧毁它们所撞击的任何东西。她为自己的安全感到焦虑,发誓要找到一个安全的地方(一个永远不会被流星摧毁的地方)。
如果将牧场放入一个直角坐标系中,贝茜现在的位置是原点,并且,贝茜不能踏上一块被流星砸过的土地。
根据预报,一共有 M M M 颗流星会坠落在农场上,其中第 i i i 颗流星会在时刻 T i T_i Ti 砸在坐标为 ( X i , Y i ) (X_i, Y_i) (Xi,Yi) 的格子里。流星的力量会将它所在的格子,以及周围 4 4 4 个相邻的格子都化为焦土,当然贝茜也无法再在这些格子上行走。
贝茜在时刻 0 0 0 开始行动,它只能在第一象限中,平行于坐标轴行动,每 1 1 1 个时刻中,她能移动到相邻的(一般是 4 4 4 个)格子中的任意一个,当然目标格子要没有被烧焦才行。如果一个格子在时刻 t t t 被流星撞击或烧焦,那么贝茜只能在 t t t 之前的时刻在这个格子里出现。 贝茜一开始在 ( 0 , 0 ) (0, 0) (0,0)
请你计算一下,贝茜最少需要多少时间才能到达一个安全的格子。如果不可能到达(天要亡我贝茜乎!)输出 −1

输入描述

输入文件名 meteor.in
M + 1 M + 1 M+1 行,第 1 1 1 行输入一个整数 M M M,接下来的 M M M 行每行输入三个整数分别为 X i , Y i , T i X_i, Y_i, T_i Xi,Yi,Ti

输出描述

输出文件名 meteor.out
贝茜到达安全地点所需的最短时间,如果不可能,则为 -1

样例1

输入

4
0 0 2
2 1 2
1 1 2
0 3 5

输出

5

提示

1 ≤ M ≤ 50000 1 \le M \le 50000 1M50000
0 ≤ T i ≤ 1000 0 \le T_i \le 1000 0Ti1000
0 ≤ X i , Y i , T i ≤ 300 0 \le X_i,Y_i,T_i \le 300 0Xi,Yi,Ti300

2.2 参考答案

#include <iostream>
#include <queue>
#include <cstdio>
#include <cstring>
#define e 0x3f3f3f3f
using namespace std;

struct Node
{
    int x, y;
    int step;
};

int n;
int x, y, t;
int a[305][305];
queue <Node> q;
bool vis[305][305];
int dx[5] = {-1, 0, 0, 1};
int dy[5] = {0, -1, 1, 0};

void bfs()
{
    vis[0][0] = 1;
    q.push({0, 0, 0});
    
    while (!q.empty())
    {
        auto [fx, fy, fstep] = q.front();
        q.pop();
        
        if (a[fx][fy] == e)
        {
            cout << fstep;
            return;
        }
        
        for (int i = 0; i <= 3; i++)
        {
            int tx = fx+dx[i];
            int ty = fy+dy[i];
            
            if (tx>=0 && ty>=0 && vis[tx][ty]==0 && a[tx][ty]>fstep+1)
            {
                vis[tx][ty] = 1;
                q.push({tx, ty, fstep+1});
            }
        }
    }
    cout << -1;
    return;
}

int main()
{
    freopen("meteor.in", "r", stdin);
    freopen("meteor.out", "w", stdout);
    
    cin >> n;
    memset(a, e, sizeof(a));
    while (n--)
    {
        cin >> x >> y >> t;
        
        // 标记爆炸时间
        a[x][y] = min(a[x][y], t);
        for (int i = 0; i <= 3; i++)
        {
            int tx = x+dx[i];
            int ty = y+dy[i];
            if (tx>=0 && ty>=0)
                a[tx][ty] = min(a[tx][ty], t);
        }
    }
    
    bfs();
    
    fclose(stdin);
    fclose(stdout);
    return 0;
}

3. 迷宫游戏

3.1 审题

题目描述

小明在玩一款迷宫游戏,在游戏中他要控制自己的角色离开一间由 N × N N \times N N×N 个格子组成的二维迷宫。
小明的起始位置在左上角,他需要到达右下角的格子才能离开迷宫。
每一步,他可以移动到上下左右相邻的格子中(前提是目标格子可以经过)。
迷宫中有些格子小明可以经过,我们用 '.' 表示;
有些格子是墙壁,小明不能经过,我们用 '#' 表示。
此外,有些格子上有陷阱,我们用 'X' 表示。除非小明处于无敌状态,否则不能经过。
有些格子上有无敌道具,我们用 '%' 表示。
当小明第一次到达该格子时,自动获得无敌状态,无敌状态会持续 K K K 步,无敌时间不能累加。
之后如果再次到达该格子不会获得无敌状态了。
处于无敌状态时,可以经过有陷阱的格子,但是不会拆除 / 毁坏陷阱,即陷阱仍会阻止没有无敌状态的角色经过。
给定迷宫,请你计算小明最少经过几步可以离开迷宫。

输入描述

第一行包含两个整数 N N N K K K。( 1 ≤ N ≤ 1000 1 \le N \le 1000 1N1000 1 ≤ K ≤ 10 1 \le K \le 10 1K10)。
以下 N N N 行包含一个 N × N N \times N N×N 的矩阵。
矩阵保证左上角和右下角是 '.'

输出描述

一个整数表示答案。如果小明不能离开迷宫,输出 -1

样例1

输入

5 3
...XX
##%#.
...#.
.###.
.....

输出

10

3.2 思路

首先,Node 的参数就应该改为: x , y , step , state x,y,\text{step},\text{state} x,y,step,state,分别表示 x x x 坐标、 y y y 坐标、当前步数、剩余的无敌状态时间。

然后,我们来分析一下 bfs() 如何修改:

  • 边界以外的排除(#+
  • 当没有标记的时候
    • ·
      • fiv>0fiv 是队首的剩余无敌时间)
        • {tx, ty, fstep+1, fiv-1} 入队
      • fiv=0
        • {tx, ty, fstep+1, 0} 入队
    • X
      • fiv>0
        • {tx, ty, fstep+1, fiv-1} 入队
      • fiv<=0
        • 不能走
    • %
      • {tx, ty, fstep+1, k} 入队

2.3 参考答案

#include <iostream>
#include <queue>
using namespace std;

struct Node
{
    int x, y;
    int step;
    int iv;
};

int n, k;
char a[1005][1005];
bool vis[1005][1005];
int dx[5] = {-1, 0, 0, 1};
int dy[5] = {0, -1, 1, 0};
queue <Node> q;

void bfs()
{
    vis[1][1] = 1;
    q.push({1, 1, 0, 0});
    
    while (!q.empty())
    {
        auto [fx, fy, fstep, fiv] = q.front();
        q.pop();
        
        if (fx==n && fy==n)
        {
            cout << fstep;
            return;
        }
        
        for (int i = 0; i <= 3; i++)
        {
            int tx = fx+dx[i];
            int ty = fy+dy[i];
            
            if (tx<1 || tx>n || ty<1 || ty>n || a[tx][ty]=='#')
                continue;
            
            if (vis[tx][ty] == 0)
            {
                vis[tx][ty] = 1;
                if (a[tx][ty] == '.')
                {
                    if (fiv > 0) q.push({tx, ty, fstep+1, fiv-1});
                    else if (fiv == 0) q.push({tx, ty, fstep+1, 0});
                }
                if (a[tx][ty] == 'X')
                    if (fiv > 0) q.push({tx, ty, fstep+1, fiv-1});
                if (a[tx][ty] == '%')
                    q.push({tx, ty, fstep+1, k});
            }
            else
                if (fiv > 0) q.push({tx, ty, fstep+1, fiv-1});
        }
    }
    cout << -1;
    return;
}

int main()
{
    cin >> n >> k;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            cin >> a[i][j];
    
    bfs();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值