加载中...

信息奥赛题单|BFS


BFS

题单 🧑🏻‍💻

题目来源 题目名称 题目链接 备注 难度
信息学奥赛一本通 - 1252 走迷宫 链接🔗 BFS与迷宫 🟢
洛谷 - P1443 马的遍历 链接🔗 BFS与迷宫 🟢
洛谷 - P1746 离开中山路 链接🔗 BFS与迷宫 🟢
洛谷 - P1747 好奇怪的游戏 链接🔗 BFS与迷宫 🟢
洛谷 - P3395 路障 链接🔗 BFS与迷宫 🟡
CodeForces - 1873E Serial Time! 链接🔗 BFS与三维迷宫 🟡
CodeForces - 196B Infinite Maze 链接🔗 BFS与迷宫 🟡

题解 🚀

走迷宫

题目信息 📚

【题目名称】信息学奥赛一本通 1252 - 走迷宫
【题目描述】

一个迷宫由 $R$ 行 $C$ 列格子组成,有的格子里有障碍物,不能走;有的格子是空地,可以走。

给定一个迷宫,求从左上角走到右下角最少需要走多少步(数据保证一定能走到)。

只能在水平方向或垂直方向走,不能斜着走。

【输入】

第一行是两个整数,$R$ 和 $C$,代表迷宫的长和宽。

接下来是 $R$ 行,每行 $C$ 个字符,代表整个迷宫。

空地格子用 ‘.’ 表示,有障碍物的格子用 ‘#’ 表示。

迷宫左上角和右下角都是 ‘.’。

【输出】

输出从左上角走到右下角至少要经过多少步(即至少要经过多少个空地格子),计算步数要包括起点和终点。

【输入样例】
5 5
..###
#....
#.#.#
#.#.#
#.#..
【输出样例】
9
【数据范围】

$1 \le R,C \le 40$

【题目链接】

http://ybt.ssoier.cn:8088/problem_show.php?pid=1252


题目解析 🍉

【题目分析】

BFSの经典迷宫题

【 C++ 代码】
#include<bits/stdc++.h>

using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 100;
char g[N][N]; // 存储迷宫
int d[N][N]; // 存储每个点到终点距离
int dx[4] = {-1, +1, 0, 0};
int dy[4] = {0, 0, -1, +1};
int n, m;

// bfs搜索
int bfs(int sx, int sy, int tx, int ty) {
    // 初始化队列和距离数组
    queue<PII> q;
    memset(d, -1, sizeof(d));

    // 加入起点
    q.push({sx, sy});
    d[sx][sy] = 1;

    // bfs
    while (!q.empty()) {
        // 取出当前队首元素进行验证,并弹出该元素
        PII t = q.front();
        if (t.first == tx && t.second == ty) return d[t.first][t.second];
        q.pop();

        // 将该元素的周边元素入队
        for (int i = 0; i < 4; i++) {
            int tmpx = t.first + dx[i];
            int tmpy = t.second + dy[i];
            // 拓展的点未被访问,且不是障碍物,下标不越界,则将该点加入队列
            if (d[tmpx][tmpy] == -1 && g[tmpx][tmpy] != '#' && tmpx >= 1 && tmpx <= n && tmpy >= 1 && tmpy <= m) {
                q.push({tmpx, tmpy});
                d[tmpx][tmpy] = d[t.first][t.second] + 1;
            }
        }
    }
    return -1;

}

int main() {
    ios::sync_with_stdio(false);  //cin读入优化
    cin.tie(0);

    // 读入迷宫
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            cin >> g[i][j];

    // bfs搜索
    cout << bfs(1, 1, n, m) << endl;

    return 0;
}

马的遍历

题目信息 📚

【题目名称】洛谷 P1443 - 马的遍历
【题目描述】

有一个 $n \times m$ 的棋盘,在某个点 $(x, y)$ 上有一个马,要求你计算出马到达棋盘上任意一个点最少要走几步。

【输入】

输入只有一行四个整数,分别为 $n, m, x, y$。

【输出】

一个 $n \times m$ 的矩阵,代表马到达某个点最少要走几步(不能到达则输出 $-1$)。

【输入样例】
3 3 1 1
【输出样例】
0    3    2    
3    -1   1    
2    1    4    
【数据规模】

对于全部的测试点,保证 $1 \leq x \leq n \leq 400$,$1 \leq y \leq m \leq 400$。

【题目链接】

https://www.luogu.com.cn/problem/P1443


题目解析 🍉

【题目分析】

BFS 与 象棋马 的奇妙缘分

【 C++ 代码】
#include<bits/stdc++.h>

using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 450, DIR = 8;
int dx[DIR] = {+1, +1, +2, +2, -1, -1, -2, -2}; // 马的方向
int dy[DIR] = {+2, -2, +1, -1, +2, -2, +1, -1};
int d[N][N];  // 距离数组
int n, m;  // 棋盘大小

// bfs搜索更新距离数组d
void bfs(int sx, int sy) {
    // 初始化队列以及距离数组
    queue<PII> q;
    memset(d, -1, sizeof(d));

    // 加入起点
    q.push({sx, sy});
    d[sx][sy] = 0;

    // bfs搜索
    while (!q.empty()) {
        // 弹出队首元素
        PII t = q.front();
        q.pop();

        // 遍历所有马可能走的位置
        for (int i = 0; i < DIR; i++) {
            int tmpx = t.first + dx[i];
            int tmpy = t.second + dy[i];
            // 拓展的点未被访问,且下标不越界,则将该点加入队列
            if (d[tmpx][tmpy] == -1 && tmpx >= 1 && tmpx <= n && tmpy >= 1 && tmpy <= m) {
                q.push({tmpx, tmpy});
                d[tmpx][tmpy] = d[t.first][t.second] + 1;
            }
        }
    }

    // 输出距离数组
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++)
            cout << left << setw(5) << d[i][j] << " "; // 宽度为5,左对齐
        // cout << setw(5) << d[i][j] << " ";  // 宽度为5,右对齐
        cout << endl;
    }
}

int main() {
    ios::sync_with_stdio(false);  //cin读入优化
    cin.tie(0);

    int x, y; // 马的初始位置
    cin >> n >> m >> x >> y;
    bfs(x, y);

    return 0;
}

离开中山路

题目信息 📚

【题目名称】洛谷 P1746 - 离开中山路
【题目描述】

爱与愁大神买完东西后,打算坐车离开中山路。现在爱与愁大神在 $x_1,y_1$ 处,车站在 $x_2,y_2$ 处。现在给出一个 $n \times n(n \le 1000)$ 的地图,$0$ 表示马路,$1$ 表示店铺(不能从店铺穿过),爱与愁大神只能垂直或水平着在马路上行进。爱与愁大神为了节省时间,他要求最短到达目的地距离(每两个相邻坐标间距离为 $1$)。你能帮他解决吗?

【输入】

第 $1$ 行包含一个数 $n$。

第 $2$ 行到第 $n+1$ 行:整个地图描述($0$ 表示马路,$1$ 表示店铺,注意两个数之间没有空格)。

第 $n+2$ 行:四个数 $x_1,y_1,x_2,y_2$。

【输出】

只有 $1$ 行,即最短到达目的地距离。

【输入样例】
3
001
101
100
1 1 3 3
【输出样例】
4
【数据范围】

对于 $20%$ 数据,满足 $1\leq n \le 100$。

对于 $100%$ 数据,满足 $1\leq n \le 1000$。

【题目链接】

https://www.luogu.com.cn/problem/P1747


题目解析 🍉

【题目分析】

BFSの经典迷宫题。

【 C++ 代码】
#include<bits/stdc++.h>

using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 1100;
char g[N][N];
int dx[4] = {-1, +1, 0, 0};
int dy[4] = {0, 0, +1, -1};
int d[N][N];  // // 距离数组
int n;

// bfs搜索
int bfs(int sx, int sy, int tx, int ty) {
    // 初始化队列和距离数组
    queue<PII> q;
    memset(d, -1, sizeof(d));
    // 加入起点
    q.push({sx, sy});
    d[sx][sy] = 0;

    while (!q.empty()) {
        // 取出当前队首元素进行验证,并弹出该元素
        PII t = q.front();
        if (t.first == tx && t.second == ty) return d[t.first][t.second];
        q.pop();

        // 将该元素的周边元素入队
        for (int i = 0; i < 4; i++) {
            int tmpx = t.first + dx[i];
            int tmpy = t.second + dy[i];
            // 拓展的点未被访问,且不是障碍物,下标不越界,则将该点加入队列
            if (d[tmpx][tmpy] == -1 && g[tmpx][tmpy] != '1' && tmpx >= 1 && tmpx <= n && tmpy >= 1 && tmpy <= n) {
                d[tmpx][tmpy] = d[t.first][t.second] + 1;
                q.push({tmpx, tmpy});
            }
        }
    }
    return -1;
}

int main() {
    ios::sync_with_stdio(false);  //cin读入优化
    cin.tie(0);

    // 读入矩阵
    cin >> n;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            cin >> g[i][j];

    int sx, sy, tx, ty;
    cin >> sx >> sy >> tx >> ty;
    cout << bfs(sx, sy, tx, ty) << endl;

    return 0;
}

好奇怪的游戏

题目信息 📚

【题目名称】洛谷 P1747 - 好奇怪的游戏
【题目描述】

爱与愁大神坐在公交车上无聊,于是玩起了手机。一款奇怪的游戏进入了爱与愁大神的眼帘:***(游戏名被打上了马赛克)。

这个游戏类似象棋,但是只有黑白马各一匹,在点 $x_1,y_1$ 和 $x_2,y_2$ 上。它们得从点 $x_1,y_1$ 和 $x_2,y_2$ 走到 $(1,1)$。这个游戏与普通象棋不同的地方是:马可以走“日”,也可以像象走“田”。

现在爱与愁大神想知道两匹马到 $(1,1)$ 的最少步数,你能帮他解决这个问题么?

【输入】

第一行两个整数 $x_1,y_1$。

第二行两个整数 $x_2,y_2$。

【输出】

第一行一个整数,表示黑马到 $(1,1)$ 的步数。

第二行一个整数,表示白马到 $(1,1)$ 的步数。

【输入样例】
12 16
18 10
【输出样例】
8 
9
【数据范围】

对于 $100%$ 数据,$x_1,y_1,x_2,y_2 \le 20$。

【题目链接】

https://www.luogu.com.cn/problem/P1747


题目解析 🍉

【题目分析】

BFSの经典迷宫题的变形,无障碍物设置。

重点思考:棋子是否只能往靠近 $(1,1)$ 的方向跳,还是可以往远离 $(1,1)$ 的方向跳(往其他方向跳,可能距离$(1,1)$ 越来越远,这是否影响答案)?

【 C++ 代码】
#include<bits/stdc++.h>

using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 100;
// 马 + 象的走法
int dx[12] = {-1, -1, +1, +1, +2, +2, -2, -2, -2, -2, +2, +2};
int dy[12] = {-2, +2, +2, -2, -1, +1, -1, +1, +2, -2, +2, -2};
int d[N][N];  // // 距离数组

// bfs搜索
int bfs(int sx, int sy, int tx, int ty) {
    // 初始化队列和距离数组
    queue<PII> q;  // 多次bfs,所以队列定义在函数内,否则每次都要清空
    memset(d, -1, sizeof(d));
    // 加入起点
    q.push({sx, sy});
    d[sx][sy] = 0;

    while (!q.empty()) {
        // 取出当前队首元素进行验证,并弹出该元素
        PII t = q.front();
        if (t.first == tx && t.second == ty) return d[t.first][t.second];
        q.pop();

        // 将该元素的周边元素入队
        for (int i = 0; i < 12; i++) {
            int tmpx = t.first + dx[i];
            int tmpy = t.second + dy[i];
            // 拓展的点未被访问,且不是障碍物,下标不越界,则将该点加入队列
            if (d[tmpx][tmpy] == -1 && tmpx >= 1 && tmpy >= 1) {
                d[tmpx][tmpy] = d[t.first][t.second] + 1;
                q.push({tmpx, tmpy});
            }
        }
    }
    return -1;
}

int main() {
    ios::sync_with_stdio(false);  //cin读入优化
    cin.tie(0);

    int black_x, black_y, white_x, white_y;
    cin >> black_x >> black_y >> white_x >> white_y;
    cout << bfs(black_x, black_y, 1, 1) << endl;
    cout << bfs(white_x, white_y, 1, 1) << endl;

    return 0;
}

Serial Time!

题目信息 📚

【题目名称】CodeForces 60B - Serial Time!
【题目描述】

The Cereal Guy’s friend Serial Guy likes to watch soap operas. An episode is about to start, and he hasn’t washed his plate yet. But he decided to at least put in under the tap to be filled with water. The plate can be represented by a parallelepiped $k ×n×m$, that is, it has $k$ layers (the first layer is the upper one), each of which is a rectangle $n × m$ with empty squares (‘.’) and obstacles (‘#’). The water can only be present in the empty squares. The tap is positioned above the square $(x, y)$ of the first layer, it is guaranteed that this square is empty. Every minute a cubical unit of water falls into the plate. Find out in how many minutes the Serial Guy should unglue himself from the soap opera and turn the water off for it not to overfill the plate. That is, you should find the moment of time when the plate is absolutely full and is going to be overfilled in the next moment.

Note: the water fills all the area within reach (see sample 4). Water flows in each of the $6$ directions, through faces of $1 × 1 × 1$ cubes.

【输入】

The first line contains three numbers $k, n, m (1 \le k, n, m \le 10)$ which are the sizes of the plate. Then follow $k$ rectangles consisting of $n$ lines each containing $m$ characters ‘.’ or ‘#’, which represents the “layers” of the plate in the order from the top to the bottom. The rectangles are separated by empty lines (see the samples). The last line contains $x$ and $y$ $(1 \le x \le n, 1 \le y \le m)$ which are the tap’s coordinates. $x$ is the number of the line and $y$ is the number of the column. Lines of each layer are numbered from left to right by the integers from $1$ to $n$, columns of each layer are numbered from top to bottom by the integers from $1$ to $m$.

【输出】

The answer should contain a single number, showing in how many minutes the plate will be filled.

【输入样例1】
1 1 1

.

1 1
【输出样例1】
1
【输入样例2】
2 1 1

.

#

1 1
【输出样例2】
1
【输入样例3】
2 2 2

.#
##

..
..

1 1
【输出样例3】
5
【输入样例4】
3 3 3

.#.
###
##.

.##
###
##.

...
...
...

1 1
【输出样例4】
13
【题目链接】

https://codeforces.com/problemset/problem/60/B


题目解析 🍉

【题目分析】

该题本质是在三维迷宫内,求包含起点的连通块点个数,BFS和DFS均可(DFS需要在数据范围较小的情况下使用)

【 C++ 代码】
#include<bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 15;
typedef struct node {  // 定义一个三维点坐标
    int x, y, z;
} node;
int walk[6][3] = {{+1, 0,  0},  // 水流的六个方向
                  {-1, 0,  0},
                  {0,  +1, 0},
                  {0,  -1, 0},
                  {0,  0,  +1},
                  {0,  0,  -1}};
char g[N][N][N];  // 盘子(三维迷宫)
int d[N][N][N];  // 每个格子被浇灌的时间(三维距离数组)
int k, n, m, timer;

// bfs搜索
void bfs(int sx, int sy, int sz) {
    // 初始化队列和距离数组
    queue<node> q;
    memset(d, -1, sizeof(d));

    // 加入起点,并且开始计时
    q.push({sx, sy, sz});
    d[sx][sy][sz] = 1;
    timer = 1;

    while (!q.empty()) {
        // 取出队首元素,并且弹出
        int x = q.front().x, y = q.front().y, z = q.front().z;
        q.pop();

        // 往六个方向拓展
        for (int i = 0; i < 6; i++) {
            int dx = x + walk[i][0], dy = y + walk[i][1], dz = z + walk[i][2];
            // 越界
            if (dx < 1 || dx > k || dy < 1 || dy > n || dz < 1 || dz > m) continue;
            // 障碍物
            if (g[dx][dy][dz] == '#') continue;
            // 已经被访问过
            if (d[dx][dy][dz] != -1) continue;

            // 加入满足条件的拓展点,并更新时间
            q.push({dx, dy, dz});
            d[dx][dy][dz] = ++timer;
        }
    }
    cout << timer << endl;
}

int ans = 0;
// dfs搜索
void dfs(int sx, int sy, int sz) {
    if (sx < 1 || sx > k || sy < 1 || sy > n || sz < 1 || sz > m || g[sx][sy][sz] == '#')
        return;
    ans++;
    g[sx][sy][sz] = '#';

    for (int i = 0; i < 6; i++) {
        dfs(sx + walk[i][0], sy + walk[i][1], sz + walk[i][2]);
    }
}

int main() {
    ios::sync_with_stdio(false);  //cin读入优化
    cin.tie(0);

    // 读入三维迷宫
    cin >> k >> n >> m;
    for (int i = 1; i <= k; i++) {
        for (int j = 1; j <= n; ++j) {
            for (int l = 1; l <= m; ++l) {
                cin >> g[i][j][l];
            }
        }
    }

    // 输入灌水起点
    int sx = 1, sy, sz;
    cin >> sy >> sz;
    // bfs统计包含起点的连通块总点数
    bfs(sx, sy, sz);
    // dfs统计包含起点的连通块总点数
    //dfs(sx, sy, sz);
    //cout << ans << endl;

    return 0;
}

Infinite Maze

题目信息 📚

【题目名称】CodeForces 196B - Infinite Maze
【题目描述】

We’ve got a rectangular n × m-cell maze. Each cell is either passable, or is a wall (impassable). A little boy found the maze and cyclically tiled a plane with it so that the plane became an infinite maze. Now on this plane cell (x, y) is a wall if and only if cell (x mod n, y mod m) is a wall.

In this problem (a mod b) is a remainder of dividing number a by number b.

The little boy stood at some cell on the plane and he wondered whether he can walk infinitely far away from his starting position. From cell (x, y) he can go to one of the following cells: (x, y - 1), (x, y + 1), (x - 1, y) and (x + 1, y), provided that the cell he goes to is not a wall.

【输入】

The first line contains two space-separated integers n and m $(1 \le n, m \le 1500)$ — the height and the width of the maze that the boy used to cyclically tile the plane.

Each of the next n lines contains m characters — the description of the labyrinth. Each character is either a “#”, that marks a wall, a “.”, that marks a passable cell, or an “S”, that marks the little boy’s starting point.

The starting point is a passable cell. It is guaranteed that character “S” occurs exactly once in the input.

【输出】

Print “Yes” (without the quotes), if the little boy can walk infinitely far from the starting point. Otherwise, print “No” (without the quotes).

【输入样例1】
5 4
##.#
##S#
#..#
#.##
#..#
【输出样例1】
Yes
【输入样例2】
5 4
##.#
##S#
#..#
..#.
#.##
【输出样例2】
No
【题目链接】

https://codeforces.com/problemset/problem/196/B


题目解析 🍉

【题目分析】

  • 尝试1:找到所有与S连通的出口,判断出口是否能够匹配

    • 通过题目样例,但是提交时 WA(测试点4 ❌)

      // test4
      // 输入
      3 3
      ...
      ###
      #S#
      // 输出
      YES
  • 尝试2:扩展地图至 $3n * 3m$,找到所有与S连通的出口,判断出口是否能够匹配

    • 通过题目样例,但是提交时 WA(测试点25 ❌)

      // test25
      // 输入
      12 12
      ##.#######.#
      #..#......S#
      #.#..#######
      ..#.###.....
      ##..##..####
      #..##..#####
      ..##..#.....
      ###..##.####
      ##..#...####
      #..##.######
      ..##..####..
      ##...#####.#
      // 输出
      Yes
【 C++ 代码】

WA代码 ❌

#include<bits/stdc++.h>

using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 5000;
char g[N][N]; // 存储原始迷宫
char g_ex[N][N]; // 存储扩展后的迷宫
int vis[N][N];  // 访问标记数组
int walk[4][2] = {{-1, 0},  // 四个方向
                  {+1, 0},
                  {0,  +1},
                  {0,  -1}};
int n, m;

int main() {
    ios::sync_with_stdio(false);  //cin读入优化
    cin.tie(0);

    // 读入迷宫
    int sx, sy;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> g[i][j];
            if (g[i][j] == 'S') {
                sx = i, sy = j;
            }
        }
    }
    
    // 扩展迷宫
    int cntx = 1, cnty = 1;
    for (int i = 1; i <= 3 * n; i++) {
        for (int j = 1; j <= 3 * m; j++) {
            g_ex[i][j] = g[(i - 1) % n + 1][(j - 1) % m + 1];
        }
    }
    sx = n + sx, sy = m + sy;
    n = 3 * n, m = 3 * m;

    // bfs搜索
    // 初始化队列和访问数组
    queue<PII> q;
    memset(vis, -1, sizeof vis);

    // 加入起点
    q.push({sx, sy});
    vis[sx][sy] = 1;

    // 将满足条件的出口坐标,加入set中(使用set是因为后面要两两验证,而set的查找时间复杂度低)
    set<PII> s;
    while (!q.empty()) {
        // 弹出队首元素
        int x = q.front().first, y = q.front().second;
        q.pop();
        // 当前点在迷宫边界,为可能的出口,加入set中
        if (x == 1 || x == n || y == 1 || y == m) s.insert({x, y});
        // 将符合条件的拓展点加入队列中
        for (int i = 0; i < 4; i++) {
            int dx = x + walk[i][0], dy = y + walk[i][1];
            if (dx < 1 || dx > n || dy < 1 || dy > m) continue;
            if (g_ex[dx][dy] == '#') continue;
            if (vis[dx][dy] != -1) continue;
            q.push({dx, dy});
            vis[dx][dy] = 1;
        }
    }

    // 遍历set,验证出口是否匹配
    bool flag = false;
    set<PII>::iterator it;
    for (it = s.begin(); it != s.end(); it++) {
        int x = it->first, y = it->second;
//        cout << x << "   " << y << endl;
        if (x == 1 || x == n) {
            if (s.find({n + 1 - x, y}) != s.end()) {
                flag = true;
                break;
            }
        } else if (y == 1 || y == n) {
            if (s.find({x, m + 1 - y}) != s.end()) {
                flag = true;
                break;
            }
        }
    }
    if (flag) cout << "YES" << endl;
    else cout << "NO" << endl;

    return 0;
}

参考资料 📚


文章作者: Rickyの水果摊
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Rickyの水果摊 !
  目录