文章目录
前言
蓝桥杯官网:蓝桥杯大赛——全国大学生TMT行业赛事
✨本博客讲解 蓝桥杯C/C++ 备赛所涉及算法知识,此博客为第十二讲:图论【习题】
本篇博客所包含习题有:
👊地牢大师
👊全球变暖
👊单链表
👊大臣的旅费
图论【例题】见博客:蓝桥杯第十二讲–图论【例题】
博客内容以题代讲,通过讲解题目的做法来帮助读者快速理解算法内容,需要注意:学习算法不能光过脑,更要实践,请读者务必自己敲写一遍本博客相关代码!!!
地牢大师
题目要求
题目描述:
你现在被困在一个三维地牢中,需要找到最快脱离的出路!
地牢由若干个单位立方体组成,其中部分不含岩石障碍可以直接通过,部分包含岩石障碍无法通过。
向北,向南,向东,向西,向上或向下移动一个单元距离均需要一分钟。
你不能沿对角线移动,迷宫边界都是坚硬的岩石,你不能走出边界范围。
请问,你有可能逃脱吗?
如果可以,需要多长时间?
输入格式:
输入包含多组测试数据。
每组数据第一行包含三个整数 L , R , C L,R,C L,R,C 分别表示地牢层数,以及每一层地牢的行数和列数。
接下来是 L L L 个 R R R 行 C C C 列的字符矩阵,用来表示每一层地牢的具体状况。
每个字符用来描述一个地牢单元的具体状况。
其中, 充满岩石障碍的单元格用”#”表示,不含障碍的空单元格用”.”表示,你的起始位置用”S”表示,终点用”E”表示。
每一个字符矩阵后面都会包含一个空行。
当输入一行为”0 0 0”时,表示输入终止。
输出格式:
每组数据输出一个结果,每个结果占一行。
如果能够逃脱地牢,则输出”Escaped in x minute(s).”,其中 x x x为逃脱所需最短时间。
如果不能逃脱地牢,则输出”Trapped!”。
数据范围:
1 ≤ L , R , C ≤ 100 1≤L,R,C≤100 1≤L,R,C≤100
输入样例:
3 4 5
S....
.###.
.##..
###.#
#####
#####
##.##
##...
#####
#####
#.###
####E
1 3 3
S##
#E#
###
0 0 0
输出样例:
Escaped in 11 minute(s).
Trapped!
思路分析
也是一道十分典型的 b f s bfs bfs 类问题,和普通的 b f s bfs bfs 只是阶数从 2 2 2 维变成了 3 3 3 维,具体的操作没有任何变化,唯一不同的就是偏移量的定义要略微因为三维空间发生一些改变,具体代码逻辑同:图论【例题】见博客:蓝桥杯第十二讲–图论【例题】 中的 献给阿尔吉侬的花束
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110;
struct Point
{
int x, y, z;
};
int l, r, c;
char g[N][N][N];
int dist[N][N][N];
Point q[N * N * N];
int dx[] = {1, -1, 0, 0, 0, 0};
int dy[] = {0, 0, 1, -1, 0, 0};
int dz[] = {0, 0, 0, 0, 1, -1};
int bfs(int x, int y, int z)
{
memset(dist, -1, sizeof dist);
q[0] = {x, y, z};
dist[x][y][z] = 0;
int hh = 0, tt = 0;
while (hh <= tt )
{
auto t = q[hh ++];
for (int i = 0; i < 6; i ++ )
{
int n = t.x + dx[i], m = t.y + dy[i], p = t.z + dz[i];
if (n < 0 || n >= l || m < 0 || m >= r || p < 0 || p >= c) continue;
if (dist[n][m][p] != -1) continue;
if (g[n][m][p] == '#') continue;
dist[n][m][p] = dist[t.x][t.y][t.z] + 1;
if (g[n][m][p] == 'E') return dist[n][m][p];
q[ ++ tt] = {n, m, p};
}
}
return -1;
}
int main()
{
while (scanf("%d%d%d", &l, &r, &c), l || r || c)
{
for (int i = 0; i < l; i ++ )
for (int j = 0; j < r; j ++ )
scanf("%s", g[i][j]);
for (int i = 0; i < l; i ++ )
for (int j = 0; j < r; j ++ )
for (int k = 0; k < c; k ++ )
if (g[i][j][k] == 'S')
{
int distance = bfs(i, j, k);
if (distance != -1)
printf("Escaped in %d minute(s).\n", distance);
else puts("Trapped!");
}
}
return 0;
}
全球变暖
题目要求
题目描述:
你有一张某海域 N × N N×N N×N 像素的照片,”.”表示海洋、”#”表示陆地,如下所示:
.......
.##....
.##....
....##.
..####.
...###.
.......
其中”上下左右”四个方向上连在一起的一片陆地组成一座岛屿,例如上图就有 2 座岛屿。
由于全球变暖导致了海面上升,科学家预测未来几十年,岛屿边缘一个像素的范围会被海水淹没。
具体来说如果一块陆地像素与海洋相邻(上下左右四个相邻像素中有海洋),它就会被淹没。
例如上图中的海域未来会变成如下样子:
.......
.......
.......
.......
....#..
.......
.......
请你计算:依照科学家的预测,照片中有多少岛屿会被完全淹没。
输入格式:
第一行包含一个整数 N N N。
以下 N N N 行 N N N 列,包含一个由字符”#”和”.”构成的 N × N N×N N×N 字符矩阵,代表一张海域照片,”#”表示陆地,”.”表示海洋。
照片保证第 1 1 1 行、第 1 1 1 列、第 N N N 行、第 N N N 列的像素都是海洋。
输出格式:
一个整数表示答案。
数据范围:
1 ≤ N ≤ 1000 1≤N≤1000 1≤N≤1000
输入样例1:
7
.......
.##....
.##....
....##.
..####.
...###.
.......
输出样例1:
1
输入样例2:
9
.........
.##.##...
.#####...
.##.##...
.........
.##.#....
.#.###...
.#..#....
.........
输出样例2:
1
思路分析
模型可以直接看出来是属于
f
l
o
o
d
flood
flood
f
i
l
l
fill
fill 模型,关于
f
l
o
o
d
flood
flood
f
i
l
l
fill
fill,可以看博客:Flood Fill,当然你不知道这个算法,单独会
b
f
s
bfs
bfs 也是没啥问题的,从
b
f
s
bfs
bfs 的角度去看这个题目,对于每一个岛屿,首先我们需要知道有多少个 #
,这个我们在
b
f
s
bfs
bfs 的过程中进行统计计算,即每次碰到一个 #
,让我们的 total ++;
即可,同时,我们还需要看这个 #
是否是周围临海的,如果周围临海,证明这块会被海水所吞掉,那么我们就让 bound ++;
,对于每一块岛屿是否完全被海水所吞灭即判断
t
o
t
a
l
total
total 和
b
o
u
n
d
bound
bound 是否相等。
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 1010;
int n;
char g[N][N];
bool st[N][N];
queue<PII> q;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, -1, 0, 1};
void bfs(int sx, int sy, int &total, int &bound)
{
q.push({sx, sy});
st[sx][sy] = true;
while (q.size())
{
auto t = q.front();
q.pop();
bool is_bound = false;
for (int i = 0; i < n; i ++ )
{
int x = t.x + dx[i], y = t.y + dy[i];
if (x < 0 || x >= n || y < 0 || y >= n) continue;
if (st[x][y]) continue;
if (g[x][y] == '.')
{
is_bound = true;
continue;
}
q.push({x, y});
st[x][y] = true;
}
total ++;
if (is_bound) bound ++;
}
}
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i ++ ) scanf("%s", g[i]);
int cnt = 0;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
if (!st[i][j] && g[i][j] == '#')
{
int total = 0, bound = 0;
bfs(i, j, total, bound);
if (total == bound) cnt ++;
}
printf("%d\n", cnt);
return 0;
}
单链表
题目要求
题目描述:
实现一个单链表,链表初始为空,支持三种操作:
- 向链表头插入一个数;
- 删除第 k k k 个插入的数后面的数;
- 在第 k k k 个插入的数后插入一个数。
现在要对该链表进行 M M M 次操作,进行完所有操作后,从头到尾输出整个链表。
注意: 题目中第 k k k 个插入的数并不是指当前链表的第 k k k 个数。例如操作过程中一共插入了 n n n 个数,则按照插入的时间顺序,这 n n n 个数依次为:第 1 1 1 个插入的数,第 2 2 2 个插入的数,…第 n n n 个插入的数。
输入格式:
第一行包含整数 M M M,表示操作次数。
接下来 M M M 行,每行包含一个操作命令,操作命令可能为以下几种:
H x
,表示向链表头插入一个数 x x x。D k
,表示删除第 k k k 个插入的数后面的数(当 k k k 为 0 0 0 时,表示删除头结点)。I k x
,表示在第 k k k 个插入的数后面插入一个数 x x x(此操作中 k k k 均大于 0 0 0)。
输出格式:
共一行,将整个链表从头到尾输出。
数据范围:
1
≤
M
≤
100000
1≤M≤100000
1≤M≤100000
所有操作保证合法。
输入样例:
10
H 9
I 1 1
D 1
D 0
H 6
I 3 6
I 4 5
I 4 5
I 3 4
D 6
输出样例:
6 4 6 5
思路分析
模板类题目,对插入和删除操作必须做到能够快速打出代码的程度。
代码
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 100010;
int head, idx;
int e[N], ne[N];
void add_head(int x)
{
e[idx] = x, ne[idx] = head, head = idx ++;
}
void remove(int k)
{
ne[k] = ne[ne[k]];
}
void add_k(int k, int x)
{
e[idx] = x, ne[idx] = ne[k], ne[k] = idx ++;
}
int main()
{
head = -1;
int m;
scanf("%d", &m);
while (m -- )
{
char op[2];
scanf("%s", op);
if (op[0] == 'H')
{
int x;
scanf("%d", &x);
add_head(x);
}
else if (op[0] == 'D')
{
int k;
scanf("%d", &k);
if (!k) head = ne[head];
else remove(k - 1);
}
else
{
int k, x;
scanf("%d%d", &k, &x);
add_k(k - 1, x);
}
}
for (int i = head; ~i; i = ne[i]) printf("%d ", e[i]);
return 0;
}
大臣的旅费
题目要求
题目描述:
很久以前, T T T王国空前繁荣。
为了更好地管理国家,王国修建了大量的快速路,用于连接首都和王国内的各大城市。
为节省经费, T T T国的大臣们经过思考,制定了一套优秀的修建方案,使得任何一个大城市都能从首都直接或者通过其他大城市间接到达。
同时,如果不重复经过大城市,从首都到达每个大城市的方案都是唯一的。
J J J 是 T T T国重要大臣,他巡查于各大城市之间,体察民情。
所以,从一个城市马不停蹄地到另一个城市成了 J J J 最常做的事情。
他有一个钱袋,用于存放往来城市间的路费。
聪明的 J J J 发现,如果不在某个城市停下来修整,在连续行进过程中,他所花的路费与他已走过的距离有关,在走第 x x x 千米到第 x + 1 x+1 x+1 千米这一千米中( x x x 是整数),他花费的路费是 x + 10 x+10 x+10 这么多。也就是说走 1 1 1 千米花费 11 11 11,走 2 2 2 千米要花费 23 23 23。
J J J 大臣想知道:他从某一个城市出发,中间不休息,到达另一个城市,所有可能花费的路费中最多是多少呢?
输入格式:
输入的第一行包含一个整数 n n n,表示包括首都在内的 T T T王国的城市数。
城市从 1 1 1 开始依次编号, 1 1 1 号城市为首都。
接下来 n − 1 n−1 n−1 行,描述T国的高速路( T T T 国的高速路一定是 n − 1 n−1 n−1 条)。
每行三个整数 P i , Q i , D i P_i,Q_i,D_i Pi,Qi,Di,表示城市 P i P_i Pi 和城市 Q i Q_i Qi 之间有一条双向高速路,长度为 D i D_i Di 千米。
输出格式:
输出一个整数,表示大臣 J J J 最多花费的路费是多少。
数据范围:
1
≤
n
≤
105
,
1≤n≤105,
1≤n≤105,
1
≤
P
i
,
Q
i
≤
n
,
1≤P_i,Q_i≤n,
1≤Pi,Qi≤n,
1
≤
D
i
≤
1000
1≤D_i≤1000
1≤Di≤1000
输入样例:
5
1 2 2
1 3 1
2 4 5
2 5 4
输出样例:
135
思路分析
本题提供三种代码,主要是写法不同:分别用链表以及 v e c t o r vector vector 实现,分别用 d f s dfs dfs 以及 b f s bfs bfs 实现,但是就思路而言,没有区别,用链表是让读者熟练链表的操作,对于 b f s bfs bfs 的代码读者可以理解了 d f s dfs dfs 之后自己写一遍,看看是否能 改写 成功。
思路:本题其实就是求树的直径,分析如下:如果不重复经过大城市,从首都到达每个大城市的方案都是唯一的。这句话意味着本题是不存在环的结构并且各个点之间是联通的,因为一旦存在环的结构,那么路径显然就不是唯一确定的了(比如可以不过环直接到达,也可以在环里绕好几圈再到达),故本题的数据结构是 树,题目求的是:最大消费,根据本题的消费公式:假设走了 s s s 段,那么费用为: ( 1 + 10 ) + ( 2 + 10 ) + . . . + ( s + 10 ) = ( 1 + 2 + . . . + s ) + s ∗ 10 = ( 1 + s ) ∗ s / 2 + s ∗ 10 (1+10) + (2+10) + ... + (s+10)=(1+2+...+s) + s*10=(1+s)*s/2 + s*10 (1+10)+(2+10)+...+(s+10)=(1+2+...+s)+s∗10=(1+s)∗s/2+s∗10,即我们如果让费用最大,那么就是让 s s s 最大,即我们求的是树的直径。
先记结论:树的直径:树中挑选任意一点(比如点 1 1 1)求其最远距离所对应的点(比如点 2 2 2),再从点 2 2 2 求一遍最远距离(比如终点为点 3 3 3),那么这个最远距离(即点 2 2 2 到点 3 3 3)就是树的直径。
结论推导:(感兴趣的可以看看)
我们使用反证法:假设上述中的
(
2
(2
(2
3
)
3)
3) 不是直径:那么显然有
d
i
s
t
[
4
]
[
5
]
>
d
i
s
t
[
2
]
[
3
]
dist[4][5] > dist[2][3]
dist[4][5]>dist[2][3],对于点
2
2
2,有点
3
3
3 是离它最远的点,即:
d
i
s
t
[
2
]
[
3
]
>
d
i
s
t
[
2
]
[
5
]
dist[2][3]>dist[2][5]
dist[2][3]>dist[2][5],则有:
d
i
s
t
[
6
]
[
3
]
>
d
i
s
t
[
6
]
[
5
]
dist[6][3]>dist[6][5]
dist[6][3]>dist[6][5],那么就会有:
d
i
s
t
[
4
]
[
6
]
+
d
i
s
t
[
6
]
[
3
]
>
d
i
s
t
[
4
]
[
6
]
+
d
i
s
t
[
6
]
[
5
]
dist[4][6]+dist[6][3]>dist[4][6]+dist[6][5]
dist[4][6]+dist[6][3]>dist[4][6]+dist[6][5],即
d
i
s
t
[
4
]
[
3
]
>
d
i
s
t
[
4
]
[
5
]
dist[4][3]>dist[4][5]
dist[4][3]>dist[4][5],但因为
(
4
(4
(4
5
)
5)
5) 是直径,即
d
i
s
t
[
4
]
[
3
]
<
=
d
i
s
t
[
4
]
[
5
]
dist[4][3]<=dist[4][5]
dist[4][3]<=dist[4][5],矛盾,故在情景一下,
(
2
(2
(2
3
)
3)
3) 是直径。
我们使用反证法:假设上述中的
(
2
(2
(2
3
)
3)
3) 不是直径:那么显然有:
d
i
s
t
[
4
]
[
5
]
>
d
i
s
t
[
2
]
[
3
]
dist[4][5]>dist[2][3]
dist[4][5]>dist[2][3],对于点
2
2
2,有点
3
3
3 是离它最远的点,即:
d
i
s
t
[
2
]
[
7
]
+
d
i
s
t
[
7
]
[
3
]
>
d
i
s
t
[
2
]
[
7
]
+
d
i
s
t
[
7
]
[
6
]
+
d
i
s
t
[
6
]
[
5
]
dist[2][7]+dist[7][3]>dist[2][7]+dist[7][6]+dist[6][5]
dist[2][7]+dist[7][3]>dist[2][7]+dist[7][6]+dist[6][5],即:
d
i
s
t
[
7
]
[
3
]
>
d
i
s
t
[
7
]
[
6
]
+
d
i
s
t
[
6
]
[
5
]
dist[7][3]>dist[7][6]+dist[6][5]
dist[7][3]>dist[7][6]+dist[6][5],则有:
d
i
s
t
[
4
]
[
3
]
=
d
i
s
t
[
4
]
[
6
]
+
d
i
s
t
[
6
]
[
7
]
+
d
i
s
t
[
7
]
[
3
]
>
d
i
s
t
[
4
]
[
6
]
+
d
i
s
t
[
6
]
[
7
]
+
d
i
s
t
[
6
]
[
5
]
=
d
i
s
t
[
6
]
[
7
]
+
d
i
s
t
[
4
]
[
5
]
>
d
i
s
t
[
4
]
[
5
]
dist[4][3]=dist[4][6]+dist[6][7]+dist[7][3]>dist[4][6]+dist[6][7]+dist[6][5]=dist[6][7]+dist[4][5]>dist[4][5]
dist[4][3]=dist[4][6]+dist[6][7]+dist[7][3]>dist[4][6]+dist[6][7]+dist[6][5]=dist[6][7]+dist[4][5]>dist[4][5],这与
(
4
(4
(4
5
)
5)
5) 是树的直径相 矛盾,故在情景二下,
(
2
(2
(2
3
)
3)
3) 是直径。
🌞🌞🌞综上所述, ( 2 (2 (2 3 ) 3) 3) 是直径。
代码 ( v e c t o r vector vector 存储的 d f s dfs dfs)
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 100010;
struct Edge
{
int id, w;
};
int n;
vector<Edge>v[N];
int dist[N];
void dfs(int u, int father, int distance)
{
dist[u] = distance;
for (auto t : v[u])
if (t.id != father)
dfs(t.id, u, dist[u] + t.w);
}
int main()
{
scanf("%d", &n);
for (int i = 0; i < n - 1; i ++ )
{
int p, q, d;
scanf("%d%d%d", &p, &q, &d);
v[p].push_back({q, d});
v[q].push_back({p, d});
}
dfs(1, -1, 0);
int u = 1;
for (int i = 1; i <= n; i ++ )
if (dist[u] < dist[i])
u = i;
dfs(u, -1, 0);
for (int i = 1; i <= n; i ++ )
if (dist[u] < dist[i])
u = i;
int s = dist[u]; // s 就是树的直径
printf("%lld\n", (1ll + s) * s / 2 + s * 10);
return 0;
}
代码 (链表存储的 d f s dfs dfs)
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 100010;
int n;
int h[N], e[2 * N], w[2 * N], ne[2 * N], idx;
int dist[N];
void add(int p, int q, int d)
{
e[idx] = q, w[idx] = d, ne[idx] = h[p], h[p] = idx ++;
}
void dfs(int u, int father, int distance)
{
dist[u] = distance;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j != father)
dfs(j, u, distance + w[i]);
}
}
int main()
{
memset(h, -1, sizeof h);
scanf("%d", &n);
for (int i = 0; i < n; i ++ )
{
int p, q, d;
scanf("%d%d%d", &p, &q, &d);
add(p, q, d);
add(q, p, d);
}
int u = 1;
dfs(1, -1, 0);
for (int i = 1; i <= n; i ++ )
if (dist[u] < dist[i])
u = i;
dfs(u, -1, 0);
for (int i = 1; i <= n; i ++ )
if (dist[u] < dist[i])
u = i;
int s = dist[u];
printf("%lld\n", s * 10 + (1ll + s) * s / 2);
return 0;
}
代码 (链表存储的 b f s bfs bfs)
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 100010;
int n;
int idx, h[N], e[2 * N], ne[2 * N], w[2 * N];
int dist[N];
queue<int> q;
void add(int p, int q, int d)
{
e[idx] = q, w[idx] = d, ne[idx] = h[p], h[p] = idx ++;
}
void bfs(int u)
{
q.push(u);
dist[u] = 0;
while (q.size())
{
auto t = q.front();
q.pop();
for (int i = h[t]; ~i; i = ne[i] )
{
int j = e[i];
if (dist[j] != -1) continue;
dist[j] = dist[t] + w[i];
q.push(j);
}
}
}
int main()
{
memset(h, -1, sizeof h);
memset(dist, -1, sizeof dist);
scanf("%d", &n);
for (int i = 0; i < n - 1; i ++ )
{
int p, q, d;
scanf("%d%d%d", &p, &q, &d);
add(p, q, d);
add(q, p, d);
}
int u = 1;
bfs(u);
for (int i = 1; i <= n; i ++ )
if (dist[u] < dist[i])
u = i;
memset(dist, -1, sizeof dist);
bfs(u);
for (int i = 1; i <= n; i ++ )
if (dist[u] < dist[i])
u = i;
int s = dist[u];
printf("%lld\n", 10 * s + (1ll + s) * s / 2);
return 0;
}