Week2Day2B:广度优先搜索的状态表示【2023 安全创客实践训练|笔记】

内容为武汉大学国家网络安全学院2022级大一第三学期“996”实训课程中所做的笔记,仅供个人复习使用,如有侵权请联系本人,将与15个工作日内将博客设置为仅粉丝可见。

目录

带钥匙的迷宫

带动态楼梯的迷宫

再探马的覆盖点

马的覆盖点

输入格式

输出格式

样例输入

样例输出

解析


带钥匙的迷宫

 我们之前已经接触过了迷宫问题,也利用 BFS 求得了迷宫问题的最短路。

这里我们接着研究更复杂一点的迷宫问题——带钥匙的迷宫。

带钥匙的迷宫就是在迷宫中除了起点、终点、障碍外还有钥匙,你需要从起点出发,取得钥匙,到达终点。我们常常求解的是带钥匙迷宫的最短路,也就是从起点去拿钥匙再到终点最少需要走多少步。

同学们可以思考一下,这个问题可以如何解决呢?

相比于普通的迷宫问题,我们就是多了钥匙这个因素,也就是说对于在同一个点的情况,现在手上有没有钥匙,对于后边是不一样的。

那我们就可以在表示状态的时候相比之前增加一维,表示现在有没有钥匙,此时的状态为 (x,y,status) ,表示目前我们在 (x,y) 这个点,目前有没有钥匙。其中 status 为 0 表示没有钥匙,为 11 表示有钥匙。

设起点为 (sx,sy) 那么初始状态就是 (sx,sy,0) ,每一次往一个方向走的时候,看这一位走到后是不是钥匙,如果是那 status 就变成 1 ,否则 status 就保持原样。

注意此时 vis 数组或者 dis 数组都需要是三维的,第三维就是 status 。


带动态楼梯的迷宫

 我们再来看一种特殊的迷宫问题,带动态楼梯的迷宫。

我们有一个迷宫,'S'是起点,'T'是终点,'*'表示障碍物,'.'表示走廊。

'|'或者'-'表示一个楼梯,并且标明了它在一开始时所处的位置:'|'表示的楼梯在最开始是竖直方向,'-'表示的楼梯在一开始是水平方向,楼梯每分钟变换一次方向。比如下面的例子中,一开始楼梯在竖直方向,一分钟以后它移动到了水平方向,再过一分钟它又回到了竖直方向。

**..T
**.*.
..|..
.*.*.
S....

注意:我们只能每次走到相邻的格子而不能斜走,每移动一次恰好为一分钟,并且我们登上楼梯并经过楼梯到达对面的整个过程只需要一分钟,我们从来不在楼梯上停留。并且每次楼梯都恰好在我们移动完毕以后才改变方向。

另外,这一分钟你也可以选择原地不动。

在这样的条件下,从起点到终点的最短路又怎么求呢?

这个问题看似复杂,实际是可以简化的。对于一个楼梯,它每一分钟变换一次,两分钟就回到原来的状态,也就是说它在奇数分钟的时候都是同一方向,偶数分钟的时候也是同一方向。

对于刚开始是-的楼梯,偶数分钟允许左右通行,奇数分钟允许上下通行。

对于刚开始是|的楼梯,偶数分钟允许上下通行,奇数分钟允许左右通行。

所以,我们可以考虑设状态为目前在哪个点,目前用的分钟是奇数还是偶数。如果这一次会走到楼梯上,那就根据楼梯的初始情况和目前时间的奇偶判断能不能走过去,能走就要沿着这个方向多走一步,因为不能停在楼梯上,而要停在空地上。

参考程序:

#include <stdio.h>
struct node {
    int x, y, step;
} q[100000];
char maze[25][25];
int vis[25][25][2];
int n, m, l, r;
int dir[5][2] = { { -1, 0}, {0, 1}, {1, 0}, {0, -1}, {0, 0}};
int in(int x, int y) {
    return 0 <= x && x < n && 0 <= y && y < m;
}
int bfs(int sx, int sy) {
    vis[sx][sy][0] = 1;
    struct node t = {sx, sy, 0};
    l = r = 0;
    q[r++] = t;
    while (l < r) {
        struct node now = q[l++];
        if (maze[now.x][now.y] == 'T') {
            return now.step;
        }
        for (int i = 0; i < 5; i++) {
            struct node nxt = now;
            nxt.x += dir[i][0];
            nxt.y += dir[i][1];
            nxt.step++;
            if (in(nxt.x, nxt.y) && maze[nxt.x][nxt.y] != '*') {
                if (maze[nxt.x][nxt.y] == '|') {
                    if (i % 2 == now.step % 2) {
                        nxt.x += dir[i][0];
                        nxt.y += dir[i][1];
                    } else {
                        continue;
                    }
                } else if (maze[nxt.x][nxt.y] == '-') {
                    if (i % 2 != now.step % 2) {
                        nxt.x += dir[i][0];
                        nxt.y += dir[i][1];
                    } else {
                        continue;
                    }
                }
            }
            if (in(nxt.x, nxt.y) && maze[nxt.x][nxt.y] != '*' && !vis[nxt.x][nxt.y][nxt.step % 2]) {
                vis[nxt.x][nxt.y][nxt.step % 2] = 1;
                q[r++] = nxt;
            }
        }
    }
    return -1;
}
int main() {
    int sx, sy;
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i++) {
        scanf("%s", maze[i]);
    }
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (maze[i][j] == 'S') {
                sx = i;
                sy = j;
            }
        }
    }
    printf("%d\n", bfs(sx, sy));
    return 0;
}

再探马的覆盖点

 我们一直以来都在用 BFS 计算最短路,不过有的时候我们遇到的问题不是求最短路,而是把从起点走 k 步以内能到的点都找到,此时我们也可以利用 BFS 求解。

那么我们就来看看这样的问题——《马的覆盖点》。

马的覆盖点

这天,蒜头君迷上了中国象棋,在和一个大师的巅峰对决中处于下风。他知道自己再走三步,大师就会赢下这一局,于是蒜头君想背水一战。

他想知道这个马走三步之内可以到达的位置,是否有好的对策可以给大师致命一击。现在蒜头君的脑子已经不足以想出马走三步之内能到达的所有位置了,于是他找到作为他好朋友的你来帮忙解决这个问题。

输入格式

第一行输入两个整数 n(1≤n≤100), m(1≤m≤100) 代表棋盘行数和列数。

第二行输入两个整数 x(1≤x≤n), y(1≤y≤m) 代表马的初始位置。

输出格式

输出整个棋盘,'.'代表棋盘上可以落子的点,'#'这个代表马走三步能到达的点。

样例输入
10 9
10 1
样例输出
.........
.........
.........
.#.#.....
#.#.#....
####.#...
#####.#..
##.###...
#.###.#..
######...

解析

这个题目在以前我们是拿 DFS 做的,状态 (x,y,step) 表示到 (x,y) 这个点走了 step 步。如果题目不限定为 3 步而告诉你是 k 步,那时间复杂度为 O(nmk) 。

现在我们试试 BFS,之前说过 BFS 是从起点一层一层往外拓展的,所以我们只需要从起点出发,进行 BFS 到三步停止就可以了。

也就是如果我们到这个点的时候已经是 3 步了,就不再往后看了,这样每个点只会被到达 1 次,即使题目问 k 步,时间复杂度也会是 O(nm) 。

参考程序:

#include <stdio.h>
struct node {
    int x, y, step;
} q[10010];
int n, m, l, r;
char maze[110][110];
int sx, sy;
int dir[8][2] = { { 2, 1}, {2, -1}, {1, 2}, {1, -2}, {-2, 1}, {-2, -1}, {-1, 2}, {-1, -2}};
int vis[110][110];
int in(int x, int y) {
    return 1 <= x && x <= n && 1 <= y && y <= m;
}
void bfs(int sx, int sy) {
    l = r = 0;
    vis[sx][sy] = 1;
    struct node t = {sx, sy, 0};
    q[r++] = t;
    while (l < r) {
        struct node now = q[l++];
        maze[now.x][now.y] = '#';
        if (now.step == 3) {
            continue;
        }
        for (int i = 0; i < 8; i++) {
            int tx = now.x + dir[i][0];
            int ty = now.y + dir[i][1];
            if (in(tx, ty) && !vis[tx][ty]) {
                vis[tx][ty] = 1;
                struct node t = {tx, ty, now.step + 1};
                q[r++] = t;
            }
        }
    }
}
int main() {
    scanf("%d%d%d%d", &n, &m, &sx, &sy);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            maze[i][j] = '.';
        }
    } 
    bfs(sx, sy);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            printf("%c", maze[i][j]);
        }
        puts("");
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值