0
点赞
收藏
分享

微信扫一扫

蓝桥杯第十二讲--图论【习题】

那小那小 2022-02-09 阅读 22

文章目录

前言

蓝桥杯官网:蓝桥杯大赛——全国大学生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 1L,R,C100

输入样例:

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 1N1000

输入样例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;
}

单链表

题目要求

题目描述:

实现一个单链表,链表初始为空,支持三种操作:

  1. 向链表头插入一个数;
  2. 删除第 k k k 个插入的数后面的数;
  3. 在第 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 行,每行包含一个操作命令,操作命令可能为以下几种:

  1. H x,表示向链表头插入一个数 x x x
  2. D k,表示删除第 k k k 个插入的数后面的数(当 k k k 0 0 0 时,表示删除头结点)。
  3. I k x,表示在第 k k k 个插入的数后面插入一个数 x x x(此操作中 k k k 均大于 0 0 0)。

输出格式:

共一行,将整个链表从头到尾输出。

数据范围:

1 ≤ M ≤ 100000 1≤M≤100000 1M100000
所有操作保证合法。

输入样例:

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 n1 行,描述T国的高速路( T T T 国的高速路一定是 n − 1 n−1 n1 条)。

每行三个整数 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, 1n105,
1 ≤ P i , Q i ≤ n , 1≤P_i,Q_i≤n, 1Pi,Qin,
1 ≤ D i ≤ 1000 1≤D_i≤1000 1Di1000

输入样例:

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)+s10=(1+s)s/2+s10,即我们如果让费用最大,那么就是让 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;
}

举报

相关推荐

0 条评论