内容为武汉大学国家网络安全学院2022级大一第三学期“996”实训课程中所做的笔记,仅供个人复习使用,如有侵权请联系本人,将与15个工作日内将博客设置为仅粉丝可见。
目录
一维坐标的移动
- 时间限制:1000ms
- 内存限制:131072K
- 语言限制:C语言
在一个长度为 n 的坐标轴上,蒜头君想从 A 点 移动到 B 点。他的移动规则如下:
- 向前一步,坐标增加 1。
- 向后一步,坐标减少 1。
- 跳跃一步,使得坐标乘 2。
蒜头君不能移动到坐标小于 0 或大于 n 的位置。蒜头君想知道从 A 点移动到 B 点的最少步数是多少,你能帮他计算出来么?
输入格式
第一行输入三个整数 n,A,B,分别代表坐标轴长度,起始点坐标,终点坐标。(0≤A,B≤n≤5000)
输出格式
输出一个整数占一行,代表蒜头要走的最少步数。
格式说明
输出时每行末尾的多余空格,不影响答案正确性
样例输入
10 2 7
样例输出
3
我的答案
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXN 5005
int n, A, B;
int vis[MAXN];
int q[MAXN], head, tail;
void bfs() {
head = tail = 0;
q[tail++] = A;
vis[A] = 1;
while (head < tail) {
int u = q[head++];
if (u == B) {
printf("%d\n", vis[u] - 1);
return;
}
if (u + 1 <= n && !vis[u + 1]) {
vis[u + 1] = vis[u] + 1;
q[tail++] = u + 1;
}
if (u - 1 >= 0 && !vis[u - 1]) {
vis[u - 1] = vis[u] + 1;
q[tail++] = u - 1;
}
if (u * 2 <= n && !vis[u * 2]) {
vis[u * 2] = vis[u] + 1;
q[tail++] = u * 2;
}
}
}
int main() {
scanf("%d%d%d", &n, &A, &B);
bfs();
return 0;
}
仙岛求药
- 时间限制:1000ms
- 内存限制:65536K
- 语言限制:C语言
少年李逍遥的婶婶病了,王小虎介绍他去一趟仙灵岛,向仙女姐姐要仙丹救婶婶。叛逆但孝顺的李逍遥闯进了仙灵岛,克服了千险万难来到岛的中心,发现仙药摆在了迷阵的深处。迷阵由 M×N 个方格组成,有的方格内有可以瞬秒李逍遥的怪物,而有的方格内则是安全。现在李逍遥想尽快找到仙药,显然他应避开有怪物的方格,并经过最少的方格,而且那里会有神秘人物等待着他。现在要求你来帮助他实现这个目标。
输入格式
第一行输入两个非零整数 M 和 N,两者均不大于 20。M 表示迷阵行数, N 表示迷阵列数。
接下来有 M 行, 每行包含 N 个字符,不同字符分别代表不同含义:
1) '@':少年李逍遥所在的位置;2) '.':可以安全通行的方格;3) '#':有怪物的方格;4) '*':仙药所在位置。
输出格式
输出一行,该行包含李逍遥找到仙药需要穿过的最少的方格数目(计数包括初始位置的方块)。如果他不可能找到仙药, 则输出 −1。
格式说明
输出时每行末尾的多余空格,不影响答案正确性
样例输入1
8 8
.@##...#
#....#.#
#.#.##..
..#.###.
#.#...#.
..###.#.
...#.*..
.#...###
样例输出1
10
样例输入2
6 5
.*.#.
.#...
..##.
.....
.#...
....@
样例输出2
8
样例输入3
9 6
.#..#.
.#.*.#
.####.
..#...
..#...
..#...
..#...
#.@.##
.#..#.
样例输出3
-1
标程与题解:在迷宫上进行广度优先搜索,以@
为起点,*
为终点,#
为障碍物。
#include <cstdio>
#include <iostream>
#include <string>
#include <queue>
using namespace std;
int M, N;
string maze[25];
bool vis[25][25];
int dir[4][2] = {{-1, 0}, {0, -1}, {1, 0}, {0, 1}};
bool in(int x, int y) {
return 0 <= x && x < M && 0 <= y && y < N;
}
struct node {
int x, y, d;
node(int _x, int _y, int _d) {
x = _x;
y = _y;
d = _d;
}
};
int bfs(int sx, int sy) {
queue<node> q;
q.push(node(sx, sy, 0));
vis[sx][sy] = true;
while (!q.empty()) {
node now = q.front();
q.pop();
if (maze[now.x][now.y] == '*') {
return now.d;
}
for (int i = 0; i < 4; i++) {
int tx = now.x + dir[i][0];
int ty = now.y + dir[i][1];
if (in(tx, ty) && maze[tx][ty] != '#' && !vis[tx][ty]) {
vis[tx][ty] = true;
q.push(node(tx, ty, now.d + 1));
}
}
}
return -1;
}
int main() {
freopen("seek.in", "r", stdin);
freopen("seek.out", "w", stdout);
cin >> M >> N;
for (int i = 0; i < M; i++) {
cin >> maze[i];
}
int x, y;
for (int i = 0; i < M; i++) {
for (int j = 0; j < N; j++) {
if (maze[i][j] == '@') {
x = i;
y = j;
}
}
}
cout << bfs(x, y) << endl;
return 0;
}
我的答案
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXN 25
int m, n;
char map[MAXN][MAXN];
int vis[MAXN][MAXN];
int q[MAXN * MAXN][2], head, tail;
int sx, sy, ex, ey;
int dx[4] = {0, 0, -1, 1};
int dy[4] = {-1, 1, 0, 0};
void bfs() {
head = tail = 0;
q[tail][0] = sx;
q[tail][1] = sy;
tail++;
vis[sx][sy] = 1;
while (head < tail) {
int ux = q[head][0];
int uy = q[head][1];
head++;
if (ux == ex && uy == ey) {
printf("%d\n", vis[ux][uy] - 1);
return;
}
for (int i = 0; i < 4; i++) {
int vx = ux + dx[i];
int vy = uy + dy[i];
if (vx >= 0 && vx < m && vy >= 0 && vy < n && !vis[vx][vy] && map[vx][vy] != '#') {
vis[vx][vy] = vis[ux][uy] + 1;
q[tail][0] = vx;
q[tail][1] = vy;
tail++;
}
}
}
printf("-1\n");
}
int main() {
scanf("%d%d", &m, &n);
for (int i = 0; i < m; i++) {
scanf("%s", map[i]);
for (int j = 0; j < n; j++) {
if (map[i][j] == '@') {
sx = i;
sy = j;
} else if (map[i][j] == '*') {
ex = i;
ey = j;
}
}
}
bfs();
return 0;
}
吃糖的时间
- 时间限制:1000ms
- 内存限制:65536K
- 语言限制:C语言
童年的我们,将和朋友分享美好的食物作为自己的快乐。这天,C 小朋友得到了很多糖果,将要把这些糖果分给要好的朋友们。已知糖果从一个人传给另一个人需要 1 秒的时间,同一个小朋友不会重复接受糖果。
由于糖果足够多,如果某时刻某小朋友接受了糖果,他会将糖果分成若干份,同时分给那些在他身旁且还没有得到糖果的小朋友们,而且自己会吃一些糖果。由于嘴馋,小朋友们等不及将糖果发完,会在得到糖果后边吃边发。每个小朋友从接受糖果到吃完糖果需要 m 秒的时间。那么,如果第 1 秒 C 小朋友开始发糖,第多少秒所有小朋友都吃完了糖呢?
输入格式
输入第一行为三个数 n,p,c(1≤n,p≤100000),分别为小朋友数、关系数和 C 小朋友的编号。
第二行为一个数 m(m≤n×(n−1)/2)),表示小朋友吃糖的时间。下面 p 行每行两个整数,表示某两个小朋友在彼此身旁。不会有同一个关系被描述多次的情况。
输出格式
输出一个数,为所有小朋友都吃完了糖的时间。
格式说明
输出时每行末尾的多余空格,不影响答案正确性
样例输入
4 3 1
2
1 2
2 3
1 4
样例输出
5
题解:BFS 搜出从起点到每个点的最短路,找到其中的最大值加上 m 即可。
我的答案
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXN 100005
typedef struct {
int v, next;
} Edge;
Edge edge[MAXN << 1];
int head[MAXN], tot;
int n, p, c, m;
int dis[MAXN], vis[MAXN];
int q[MAXN], front, rear;
void add_edge(int u, int v) {
edge[++tot].v = v;
edge[tot].next = head[u];
head[u] = tot;
}
void bfs() {
memset(dis, 0x3f, sizeof(dis));
dis[c] = 1;
q[rear++] = c;
vis[c] = 1;
while (front != rear) {
int u = q[front++];
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].v;
if (!vis[v]) {
vis[v] = 1;
dis[v] = dis[u] + 1;
q[rear++] = v;
}
}
}
}
int main() {
scanf("%d%d%d", &n, &p, &c);
scanf("%d", &m);
for (int i = 1; i <= p; i++) {
int u, v;
scanf("%d%d", &u, &v);
add_edge(u, v);
add_edge(v, u);
}
bfs();
int ans = 0;
for (int i = 1; i <= n; i++) {
ans = ans > dis[i] ? ans : dis[i];
}
printf("%d\n", ans + m);
return 0;
}
奇怪的电梯
- 时间限制:1000ms
- 内存限制:131072K
- 语言限制:C语言
呵呵,有一天我做了一个梦,梦见了一种很奇怪的电梯。大楼的每一层楼都可以停电梯,而且第 i 层楼 (1≤i≤N) 上有一个数字Ki (0≤Ki≤N)。电梯只有四个按钮:开,关,上,下。上下的层数等于当前楼层上的那个数字。当然,如果不能满足要求,相应的按钮就会失灵。例如:3,3,1,2,5 代表了 Ki(K1=3,K2=3,…),从 1 楼开始。在 1 楼,按“上”可以到 4 楼,按“下”是不起作用的,因为没有 −2楼。那么,从 A 楼到 B 楼至少要按几次按钮呢?
输入格式
第一行为 3 个用空格隔开的正整数,表示 N,A,B (1≤N≤200,1≤A,B≤N)。
第二行为 N 个用空格隔开的非负整数,表示 Ki (1≤Ki≤100)。
输出格式
最少按键次数,若无法到达,则输出 −1。
格式说明
输出时每行末尾的多余空格,不影响答案正确性
样例输入
5 1 5
3 3 1 2 5
样例输出
3
标程与题解:使用 BFS,状态是当前所在的楼层,每次尝试按上下键,到达接下来的楼层。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
int K[205], dis[205];
queue<int> q;
int N, A, B;
void bfs(int A) {
memset(dis, -1, sizeof(dis));
dis[A] = 0;
q.push(A);
while (!q.empty()) {
int now = q.front();
q.pop();
if (now - K[now] >= 1 && dis[now - K[now]] == -1) {
dis[now - K[now]] = dis[now] + 1;
q.push(now - K[now]);
}
if (now + K[now] <= N && dis[now + K[now]] == -1) {
dis[now + K[now]] = dis[now] + 1;
q.push(now + K[now]);
}
}
}
int main() {
freopen("lift.in", "r", stdin);
freopen("lift.out", "w", stdout);
cin >> N >> A >> B;
for (int i = 1; i <= N; i++) {
cin >> K[i];
}
bfs(A);
cout << dis[B] << endl;
return 0;
}
我的答案
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXN 205
int n, a, b;
int k[MAXN];
int dis[MAXN], vis[MAXN];
int q[MAXN], front, rear;
void bfs() {
memset(dis, 0x3f, sizeof(dis));
dis[a] = 0;
q[rear++] = a;
vis[a] = 1;
while (front != rear) {
int u = q[front++];
if (u + k[u] <= n && !vis[u + k[u]]) {
vis[u + k[u]] = 1;
dis[u + k[u]] = dis[u] + 1;
q[rear++] = u + k[u];
}
if (u - k[u] >= 1 && !vis[u - k[u]]) {
vis[u - k[u]] = 1;
dis[u - k[u]] = dis[u] + 1;
q[rear++] = u - k[u];
}
}
}
int main() {
scanf("%d%d%d", &n, &a, &b);
for (int i = 1; i <= n; i++) {
scanf("%d", &k[i]);
}
bfs();
if (dis[b] == 0x3f3f3f3f) {
printf("-1\n");
} else {
printf("%d\n", dis[b]);
}
return 0;
}
密码锁
- 时间限制:1000ms
- 内存限制:262144K
- 语言限制:C语言
现在一个紧急的任务是打开一个密码锁。密码由四位数字组成,每个数字从 1 到 9 进行编号。每次可以对任何一位数字加 1 或减 1。当将9
加 1 时,数字将变为1
,当1
减 1 的时,数字将变为9
。你也可以交换相邻数字,每一个行动记做一步。现在你的任务是使用最小的步骤来打开锁。注意:最左边的数字不与最右边的数字相邻。
输入格式
第一行输入四位数字,表示密码锁的初始状态。
第二行输入四位数字,表示开锁的密码。
输出格式
输出一个整数,表示最小步骤。
格式说明
输出时每行末尾的多余空格,不影响答案正确性
样例输入
1234
2144
样例输出
2
标程与题解:此类题目是 BFS 最好的用武之地(最少的步骤),DFS 就不太方便了。
因为只是四位数,所以我们就可以把这个四位数当成状态,数组也开得下。
每次我们可以的操作有三种,第一种对于四位数字中的某一位加一,第二种对于四位数字中的某一位减一,第三种对于四位数字进行交换。
求出操作后的数,进行判断看是否需要入队。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
int dis[10005], digit[4];
queue<int> q;
int nxt(int x) {
if (x == 9) {
return 1;
}
return x + 1;
}
int pre(int x) {
if (x == 1) {
return 9;
}
return x - 1;
}
void divide(int x) {
for (int i = 0; i < 4; i++) {
digit[i] = x % 10;
x /= 10;
}
}
int get() {
int ret = 0;
for (int i = 3; i >= 0; i--) {
ret = ret * 10 + digit[i];
}
return ret;
}
void bfs(int s) {
memset(dis, -1, sizeof(dis));
dis[s] = 0;
q.push(s);
while (!q.empty()) {
int now = q.front();
q.pop();
divide(now);
for (int i = 0; i < 4; i++) {
digit[i] = nxt(digit[i]);
int nt = get();
if (dis[nt] == -1) {
dis[nt] = dis[now] + 1;
q.push(nt);
}
digit[i] = pre(digit[i]);
}
for (int i = 0; i < 4; i++) {
digit[i] = pre(digit[i]);
int nt = get();
if (dis[nt] == -1) {
dis[nt] = dis[now] + 1;
q.push(nt);
}
digit[i] = nxt(digit[i]);
}
for (int i = 0; i < 3; i++) {
swap(digit[i], digit[i + 1]);
int nt = get();
if (dis[nt] == -1) {
dis[nt] = dis[now] + 1;
q.push(nt);
}
swap(digit[i], digit[i + 1]);
}
}
}
int main() {
freopen("lock.in", "r", stdin);
freopen("lock.out", "w", stdout);
int s, t;
cin >> s >> t;
bfs(s);
cout << dis[t] << endl;
return 0;
}
我的答案
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXN 10005
int s, t;
int dis[MAXN], vis[MAXN];
int q[MAXN], front, rear;
int get(int x, int i) {
return x / (int)pow(10, i - 1) % 10;
}
int set(int x, int i, int v) {
return x + (v - get(x, i)) * (int)pow(10, i - 1);
}
void bfs() {
memset(dis, 0x3f, sizeof(dis));
dis[s] = 0;
q[rear++] = s;
vis[s] = 1;
while (front != rear) {
int u = q[front++];
for (int i = 1; i <= 4; i++) {
int v = get(u, i);
if (v == 9) {
v = set(u, i, 1);
} else {
v = set(u, i, v + 1);
}
if (!vis[v]) {
vis[v] = 1;
dis[v] = dis[u] + 1;
q[rear++] = v;
}
v = get(u, i);
if (v == 1) {
v = set(u, i, 9);
} else {
v = set(u, i, v - 1);
}
if (!vis[v]) {
vis[v] = 1;
dis[v] = dis[u] + 1;
q[rear++] = v;
}
}
for (int i = 2; i <= 4; i++) {
int a = get(u, i - 1), b = get(u, i);
int v = set(set(u, i - 1, b), i, a);
if (!vis[v]) {
vis[v] = 1;
dis[v] = dis[u] + 1;
q[rear++] = v;
}
}
}
}
int main() {
scanf("%d%d", &s, &t);
bfs();
printf("%d\n", dis[t]);
return 0;
}
奶酪
- 时间限制:1000ms
- 内存限制:262144K
- 语言限制:C语言
现有一块大奶酪,它的高度为 h,它的长度和宽度我们可以认为是无限大的,奶酪中间有许多 半径相同 的球形空洞。我们可以在这块奶酪中建立空间坐标系,在坐标系中,奶酪的下表面为 z=0,奶酪的上表面为 z=h。
现在,奶酪的下表面有一只小老鼠 Jerry,它知道奶酪中所有空洞的球心所在的坐标。如果两个空洞相切或是相交,则 Jerry 可以从其中一个空洞跑到另一个空洞,特别地,如果一个空洞与下表面相切或是相交,Jerry 则可以从奶酪下表面跑进空洞;如果一个空洞与上表面相切或是相交,Jerry 则可以从空洞跑到奶酪上表面。
位于奶酪下表面的 Jerry 想知道,在不破坏奶酪的情况下,最少经过多少个空洞才能够跑到奶酪的上表面去?
空间内两点 P1(x,y,z),P2(x,y,z) 的距离公式如下:dist(P1,P2)=√[(x1−x2)^2+(y1−y2)^2+(z1−z2)^2]
输入格式
输入文件的第一行,包含一个正整数 T,代表该输入文件中所含的数据组数。
接下来是 T 组数据,每组数据的格式如下:
第一行包含三个正整数 n,h,r,两个数之间以一个空格分开,分别代表奶酪中空洞的数量,奶酪的高度和空洞的半径。
接下来的 n 行,每行包含三个整数 x,y,z,两个数之间以一个空格分开,表示空洞球心坐标为(x,y,z)。
输出格式
输出文件包含 T 行,分别对应 T 组数据的答案,如果在第 i 组数据中,Jerry 能从下表面跑到上表面,则输出需要经过的空洞数的最小值,如果不能,则输出-1
。
数据范围
对于 20% 的数据:n=1,1≤h,r≤10,000,坐标的绝对值不超过 10,000。
对于 40% 的数据:1≤n≤8,1≤h,r≤10,000,坐标的绝对值不超过 10,000。
对于 80% 的数据:1≤n≤1,000,1≤h,r≤10,000,坐标的绝对值不超过 10,000。
对于 100% 的数据:1≤n≤1,000,1≤h,r≤1,000,000,000,T≤20,坐标的绝对值不超过 1,000,000,000。
格式说明
输出时每行末尾的多余空格,不影响答案正确性
样例输入
3
2 4 1
0 0 1
0 0 3
2 5 1
0 0 1
0 0 4
2 5 2
0 0 2
2 0 4
样例输出
2
-1
2
样例解释
第一个空洞在 (0,0,0) 与下表面相切。
第二个空洞在 (0,0,4) 与上表面相切。
两个空洞在 (0,0,2) 相切。
输出2
。
第二组数据,由奶酪的剖面图可见:
两个空洞既不相交也不相切。
输出-1
。
第三组数据,由奶酪的剖面图可见:
两个空洞相交且与上下表面相切或相交。
输出2
。
标程与题解:所有高度小于等于 r 的空洞都可以作为起点,所有高度加 r 大于等于 h 的空洞都可以作为终点,一对圆心距离不超过 2r 的空洞是互相可达的。
建图后该题变成了多起点 BFS 问题,把起点在最开始全放入队列再进行搜索就好。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
long long x[1005], y[1005], z[1005];
bool f1[1005], f2[1005];
vector<int> G[1005];
int dis[1005];
queue<int> q;
int T, n, h, r;
int bfs() {
memset(dis, -1, sizeof(dis));
for (int i = 0; i < n; i++) {
if (f1[i]) {
dis[i] = 1;
q.push(i);
}
}
while (!q.empty()) {
int now = q.front();
q.pop();
for (int i = 0; i < G[now].size(); i++) {
int v = G[now][i];
if (dis[v] == -1) {
dis[v] = dis[now] + 1;
q.push(v);
}
}
}
int ret = n + 1;
for (int i = 0; i < n; i++) {
if (f2[i] && dis[i] != -1) {
ret = min(ret, dis[i]);
}
}
if (ret == n + 1) {
return -1;
} else {
return ret;
}
}
int main() {
freopen("cheese.in", "r", stdin);
freopen("cheese.out", "w", stdout);
cin >> T;
while (T--) {
cin >> n >> h >> r;
for (int i = 0; i < n; i++) {
cin >> x[i] >> y[i] >> z[i];
}
memset(f1, false, sizeof(f1));
memset(f2, false, sizeof(f2));
for (int i = 0; i < n; i++) {
if (z[i] <= r) {
f1[i] = true;
}
if (z[i] + r >= h) {
f2[i] = true;
}
}
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if ((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]) + (z[i] - z[j]) * (z[i] - z[j]) <= 4LL * r * r) {
G[i].push_back(j);
G[j].push_back(i);
}
}
}
cout << bfs() << endl;
for (int i = 0; i < n; i++) {
vector<int>().swap(G[i]);
}
}
return 0;
}
我的答案
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <stdbool.h>
#define MAXN 1010
#define INF 1e9
typedef struct {
int x, y, z;
} Point;
int T, n, h, r;
Point holes[MAXN];
bool visited[MAXN];
bool adj_matrix[MAXN][MAXN];
double dist(Point p1, Point p2) {
double dx = p1.x - p2.x;
double dy = p1.y - p2.y;
double dz = p1.z - p2.z;
return sqrt(dx * dx + dy * dy + dz * dz);
}
int bfs() {
int queue[MAXN], head = 0, tail = 0;
int min_hole[MAXN];
for (int i = 0; i < MAXN; i++) {
min_hole[i] = INF;
}
for (int i = 0; i < n; i++) {
if (holes[i].z - r <= 0) {
visited[i] = true;
queue[tail++] = i;
min_hole[i] = 1;
}
}
while (head < tail) {
int cur = queue[head++];
if (h - holes[cur].z - r <= 0) {
return min_hole[cur];
}
for (int i = 0; i < n; i++) {
if (!visited[i] && adj_matrix[cur][i]) {
visited[i] = true;
queue[tail++] = i;
min_hole[i] = min_hole[cur] + 1;
}
}
}
return -1;
}
int main() {
scanf("%d", &T);
while (T--) {
scanf("%d %d %d", &n, &h, &r);
for (int i = 0; i < n; i++) {
scanf("%d %d %d", &holes[i].x, &holes[i].y, &holes[i].z);
}
memset(adj_matrix, 0, sizeof(adj_matrix));
memset(visited, 0, sizeof(visited));
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (dist(holes[i], holes[j]) <= 2.0 * r) {
adj_matrix[i][j] = adj_matrix[j][i] = true;
}
}
}
printf("%d\n", bfs());
}
return 0;
}