Week2Day1C:深度优先搜索练习【2023 安全创客实践训练|笔记】

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

目录

最大的蛋糕块

输入格式

输出格式

样例输入

样例输出

我的答案

中国象棋

输入格式

输出格式

样例输入

样例输出

我的答案

踏青

输入格式

输入格式

样例输入

样例输出

我的答案

连通块数量

输入格式

输出格式

样例输入

样例输出

我的答案

引爆炸弹

输入格式

输出格式

样例输入

样例输出

我的答案


最大的蛋糕块

  • 时间限制:1000ms
  • 空间限制:131072K
  • 语言限制:C语言

这一天蒜头君生日,他的朋友们一起来给蒜头君买一个大的蛋糕过生日。游戏做完后到了切蛋糕的时刻了,朋友们知道蒜头君喜欢吃蛋糕,便让蒜头君自己给自己切一块最大的。蒜头君看朋友们这么热情也就不客气了。

这块蛋糕是由 R×C 的网格构成,每个网格上面都放有不同的水果。蒜头君把这些水果分为两类,一类是自己喜欢吃的水果,用#来表示;一类是自己不喜欢吃的水果,用.来表示。

蒜头君对切出的蛋糕有如下要求:

  • 切出的蛋糕连成一块(可以不为矩形,但必须在网格上连通)
  • 切出的蛋糕只包含自己喜欢吃的水果

两个位置连通是指:在网格中,两个位置上、下、左、右四个方向中,某个方向相邻。

请问,蒜头君最大可以吃到多大的蛋糕?

输入格式

第一行输入两个被空格隔开的整数 R(1≤R≤1000) 和 C(1≤C≤1000)。

然后会有一个 R×C 的网格,由#.组成。

输出格式

输出一个整数,表示蒜头君可以吃到的蛋糕最大是多少(即对应到网格中的格子数)。

格式说明

输出时每行末尾的多余空格,不影响答案正确性

样例输入
5 6
.#....
..#...
..#..#
...##.
.#....
样例输出
2

题解:求最大的连通块,从每一个没访问过的#除法搜索求得这一次的连通块大小,每一次跟当前答案取最大,这样最后就得到了最大的连通块。

 

我的答案

#include <stdio.h>
#define MAXN 1005

int R, C;
char cake[MAXN][MAXN];
int visited[MAXN][MAXN];
int max_size = 0;

int dfs(int x, int y) {
    if (x < 0 || x >= R || y < 0 || y >= C) return 0;
    if (visited[x][y] || cake[x][y] == '.') return 0;
    visited[x][y] = 1;
    return 1 + dfs(x-1, y) + dfs(x+1, y) + dfs(x, y-1) + dfs(x, y+1);
}

int main() {
    scanf("%d %d", &R, &C);
    for (int i = 0; i < R; i++) {
        scanf("%s", cake[i]);
    }
    for (int i = 0; i < R; i++) {
        for (int j = 0; j < C; j++) {
            if (!visited[i][j] && cake[i][j] == '#') {
                int size = dfs(i, j);
                if (size > max_size) max_size = size;
            }
        }
    }
    printf("%d\n", max_size);
    return 0;
}

中国象棋

  • 时间限制:1000ms
  • 空间限制:131072K
  • 语言限制:C语言

中国象棋博大精深,其中马的规则最为复杂,也是最难操控的一颗棋子。

我们都知道象棋中马走"日",比如在 (2,4) 位置的一个马,跳一步能到达的位置有 (0,3),(0,5),(1,2),(1,6),(3,2),(3,6),(4,3),(4,5)。

蒜头君正在和花椰妹下棋,蒜头君正在进行战略布局,他需要把在 (x,y) 位置的马跳到 (x′,y′) 位置,以达到威慑的目的。

但是棋盘大小有限制,棋盘是一个 10×9 的网格,左上角坐标为 (0,0),右下角坐标为 (9,8),马不能走出棋盘,并且有些地方已经有了棋子,马也不能跳到有棋子的点。

蒜头君想知道,在不移动其他棋子的情况下,能否完成他的战略目标。

不用考虑蹩马腿的情况,如果你不知道是什么,可以忽略掉这句话。

输入格式

输入一共 10 行,每行一个长度为 9 的字符串。

输入表示这个棋盘,我们用'.'表示空位置,用'#'表示该位置有棋子,用'S'表示初始的马的位置,用'T'表示马需要跳到的位置。

输入保证一定只存在一个'S'和一个'T'

输出格式

如果在不移动其他棋子的情况下,马能从'S'跳到'T',那么输出一行"Yes",否则输出一行"No"

格式说明

输出时每行末尾的多余空格,不影响答案正确性

样例输入
.#....#S#
..#.#.#..
..##.#..#
......##.
...T.....
...#.#...
...#.....
...###...
.........
.##......
样例输出
Yes

题解:这道题目和我们前面做的题目有点不同。

1:我们可以选择的方向已经不是前面那样,只有四个方向可以选择。我们这里有 88 个方向可以选择。

2:我们可以不用像前面那样,标记完成之后再次取消标记。这里我们如果不取消标记反而可以提高代码的运行效率。因为我们这里只需要判断两个点是否可达,到达过的状态就没有必要再次到达了。

我的答案

#include <stdio.h>
#include <stdbool.h>

#define MAXN 10
#define MAXM 9

char board[MAXN][MAXM];
bool visited[MAXN][MAXM];
int dx[] = {-2, -2, -1, -1, 1, 1, 2, 2};
int dy[] = {-1, 1, -2, 2, -2, 2, -1, 1};
int sx, sy, tx, ty;

bool dfs(int x, int y) {
    if (x < 0 || x >= MAXN || y < 0 || y >= MAXM) return false;
    if (visited[x][y] || board[x][y] == '#') return false;
    if (x == tx && y == ty) return true;
    visited[x][y] = true;
    for (int i = 0; i < 8; i++) {
        int nx = x + dx[i];
        int ny = y + dy[i];
        if (dfs(nx, ny)) return true;
    }
    return false;
}

int main() {
    for (int i = 0; i < MAXN; i++) {
        scanf("%s", board[i]);
        for (int j = 0; j < MAXM; j++) {
            if (board[i][j] == 'S') {
                sx = i;
                sy = j;
            } else if (board[i][j] == 'T') {
                tx = i;
                ty = j;
            }
        }
    }
    if (dfs(sx, sy)) printf("Yes\n");
    else printf("No\n");
    return 0;
}

踏青

  • 时间限制:1000ms
  • 空间限制:131072K
  • 语言限制:C语言

蒜头君和他的朋友周末相约去召唤师峡谷踏青。他们发现召唤师峡谷的地图是由一块一块格子组成的,有的格子上是草丛,有的是空地。草丛通过上下左右 4 个方向扩展其他草丛形成一片草地,任何一片草地中的格子都是草丛,并且所有格子之间都能通过上下左右连通。如果用'#'代表草丛,'.'代表空地,下面的峡谷中有 2 片草地。

##..
..##

处在同一个草地的 2 个人可以相互看到,空地看不到草地里面的人。他们发现有一个朋友不见了,现在需要分头去找,每个人负责一片草地,蒜头君想知道他们至少需要多少人。

输入格式

第一行输入 n, m (1≤n,m≤100) 表示峡谷大小,两数之间以一个空格分隔。

接下来输入 n 行字符串表示峡谷的地形。

输入格式

输出至少需要多少人。

格式说明

输出时每行末尾的多余空格,不影响答案正确性

样例输入
5 6
.#....
..#...
..#..#
...##.
.#....
样例输出
5

标程与题解:求连通块个数,从每个还没走过的'#'出发用dfs把在同一连通块的'#'都访问,统计结果加1

#include <cstdio>
#include <iostream>
#include <algorithm>

using namespace std;
int n, m;
bool vis[1005][1005];
char maze[1005][1005];
int ans;
int dir[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
bool in(int x, int y) {
    return x >= 0 && x < n && y >= 0 && y < m;
}
void dfs(int x, int y) {
    vis[x][y] = true;
    for (int i = 0; i < 4; i++) {
        int tx = x + dir[i][0];
        int ty = y + dir[i][1];
        if (in(tx, ty) && !vis[tx][ty] && maze[tx][ty] == '#') {
            dfs(tx, ty);
        }
    }
}
int main() {
    freopen("hiking.in", "r", stdin);
    freopen("hiking.out", "w", stdout);
    cin >> n >> m;
    for (int i = 0; i < n; i++) {
        cin >> maze[i];
    }
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (maze[i][j] == '#' && !vis[i][j]) {
                dfs(i, j);
                ans++;
            }
        }
    }
    cout << ans << endl;
    return 0;
}

我的答案

#include <stdio.h>
#define MAXN 105

int n, m;
char valley[MAXN][MAXN];
int visited[MAXN][MAXN];

void dfs(int x, int y) {
    if (x < 0 || x >= n || y < 0 || y >= m) return;
    if (visited[x][y] || valley[x][y] == '.') return;
    visited[x][y] = 1;
    dfs(x-1, y);
    dfs(x+1, y);
    dfs(x, y-1);
    dfs(x, y+1);
}

int main() {
    scanf("%d %d", &n, &m);
    for (int i = 0; i < n; i++) {
        scanf("%s", valley[i]);
    }
    int count = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (!visited[i][j] && valley[i][j] == '#') {
                count++;
                dfs(i, j);
            }
        }
    }
    printf("%d\n", count);
    return 0;
}

连通块数量

  • 时间限制:1000ms
  • 空间限制:131072K
  • 语言限制:C语言

输入一个无向图,求图中连通块的个数。

输入格式

输入第一行两个整数 n,m(1≤n,m≤20000),表示图的点的数量和边的数量,两数之间以一个空格分隔。

接下来 m 行,每行两个整数 a,b(1≤a,b≤n),表示一条无向边,两数之间以一个空格分隔。

输出格式

输入一行一个整数,表示图中连通块的个数。

格式说明

输出时每行末尾的多余空格,不影响答案正确性

样例输入
5 4
2 3
4 1
5 2
2 2
样例输出
2

标程与题解:依次考虑每一个点,如果还没有访问过就说明这是一个新的连通块,给cnt1以后进入搜索。

#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
vector<int> G[20005];
bool vis[20005];
void dfs(int u) {
    vis[u] = true;
    for (int i = 0; i < G[u].size(); i++) {
        int v = G[u][i];
        if (!vis[v]) {
            dfs(v);
        }
    }
}
int main() {
    freopen("block.in", "r", stdin);
    freopen("block.out", "w", stdout);
    int n, m;
    cin >> n >> m;
    for (int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    int cnt = 0;
    for (int i = 1; i <= n; i++) {
        if (!vis[i]) {
            cnt++;
            dfs(i);
        }
    }
    cout << cnt << endl;
    return 0;
}

我的答案

#include <stdio.h>
#include <string.h>
#define MAXN 20005

int n, m;
int head[MAXN], to[MAXN << 1], nxt[MAXN << 1], tot;
int vis[MAXN];

void add(int u, int v) {
    to[++tot] = v;
    nxt[tot] = head[u];
    head[u] = tot;
}

void dfs(int u) {
    vis[u] = 1;
    for (int i = head[u]; i; i = nxt[i]) {
        int v = to[i];
        if (!vis[v]) dfs(v);
    }
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        add(u, v);
        add(v, u);
    }
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        if (!vis[i]) {
            ans++;
            dfs(i);
        }
    }
    printf("%d\n", ans);
    return 0;
}

引爆炸弹

  • 时间限制:1000ms
  • 空间限制:131072K
  • 语言限制:C语言

在一个 n×m 的方格地图上,某些方格上放置着炸弹。手动引爆一个炸弹以后,炸弹会把炸弹所在的行和列上的所有炸弹引爆,被引爆的炸弹又能引爆其他炸弹,这样连锁下去。

现在为了引爆地图上的所有炸弹,需要手动引爆其中一些炸弹,为了把危险程度降到最低,请算出最少手动引爆多少个炸弹可以把地图上的所有炸弹引爆。

输入格式

第一行输两个整数 n,m,用空格隔开。

接下来 n 行,每行输入一个长度为 m 的字符串,表示地图信息。0表示没有炸弹,1表示炸弹。

数据约定:

对于 40% 的数据:1≤n,m≤100;

对于 100% 的数据:1≤n,m≤500;

输出格式

输出一个整数,表示最少需要手动引爆的炸弹数。

格式说明

输出时每行末尾的多余空格,不影响答案正确性

样例输入
5 5
00010
00010
01001
10001
01000
样例输出
2

 

任务提示

样例的方法如下:先引手动引爆一个炸弹,红色的手动引爆,绿色的是间接引爆。

然后再手动引爆一个炸弹:

因此最少需要手动引爆两枚炸弹。

标程与题解:每一次找一个还没引爆的炸弹引爆,答案加 1 ,对于每个引爆的炸弹看这一行这一列,如果有炸弹就继续去引爆那个炸弹。

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

char mat[505][505];
int n, m;
void boom(int x, int y) {
    mat[x][y] = '0';
    for (int i = 0; i < m; ++i) {
        if (mat[x][i] == '1') {
            boom(x, i);
        }
    }
    for (int i = 0; i < n; ++i) {
        if (mat[i][y] == '1') {
            boom(i, y);
        }
    }
}

int main() {
    freopen("boom.in", "r", stdin);
    freopen("boom.out", "w", stdout);
    cin >> n >> m;
    for (int i = 0; i < n; ++i) {
        cin >> mat[i];
    }
    int cnt = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (mat[i][j] == '1') {
                cnt++;
                boom(i, j);
            }
        }
    }
    cout << cnt << endl;
    return 0;
}

我的答案

#include <stdio.h>
#include <string.h>
#define MAXN 505

int n, m;
char g[MAXN][MAXN];
int vis[MAXN][MAXN];

void dfs(int x, int y) {
    vis[x][y] = 1;
    for (int i = 1; i <= n; i++) {
        if (g[i][y] == '1' && !vis[i][y]) dfs(i, y);
    }
    for (int j = 1; j <= m; j++) {
        if (g[x][j] == '1' && !vis[x][j]) dfs(x, j);
    }
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%s", g[i] + 1);
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (g[i][j] == '1' && !vis[i][j]) {
                ans++;
                dfs(i, j);
            }
        }
    }
    printf("%d\n", ans);
    return 0;
}
  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值