内容为武汉大学国家网络安全学院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
标程与题解:依次考虑每一个点,如果还没有访问过就说明这是一个新的连通块,给cnt
加1
以后进入搜索。
#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;
}