程序设计思维与实践 Week6 作业

A 氪金带东

1. 题目大意

实验室里原先有一台电脑(编号为1),最近氪金带师咕咕东又为实验室购置了N-1台电脑,编号为2到N。每台电脑都用网线连接到一台先前安装的电脑上。但是咕咕东担心网速太慢,他希望知道第i台电脑到其他电脑的最大网线长度,但是可怜的咕咕东在不久前刚刚遭受了宇宙射线的降智打击,请你帮帮他。
在这里插入图片描述

输入:
输入文件包含多组测试数据。对于每组测试数据,第一行一个整数N (N<=10000),接下来有N-1行,每一行两个数,对于第i行的两个数,它们表示与i号电脑连接的电脑编号以及它们之间网线的长度。网线的总长度不会超过10^9,每个数之间用一个空格隔开。

输出:
对于每组测试数据输出N行,第i行表示i号电脑的答案 (1<=i<=N)

样例:

5
1 1
2 1
3 1
1 1
3
2
3
4
4

2. 思路历程

  • 要求出第i台电脑到其他电脑的最大网线长度,可以逆向考虑其他电脑到第i台电脑的最大网线长度,而最大长度肯定是与端点之间的长度。
  • 这里引入树的直径的概念,树中任意两点之间距离的最大值。
  1. 树的直径一定是某两个叶子之间的距离
  2. 从树中任选一个点开始遍历这棵树,找到一个距离这个点最远的叶子,然后再从这个叶子开始遍历,找到离这个叶子最远的另一个叶子,这两点之间的距离即树的直径。
  • 这道题的最大长度就转换成了第i台电脑到直径的两个端点的最大值,加上逆向考虑即从两个端点开始dfs,沿路径方向对每台电脑的距离进行更新。

3. 具体实现

  • 创建结构体Edge,包含当前节点、下一个节点和权重。
    这是图的链式前向星的存储方式,用数组来模拟链表,head数组模拟头指针,相比较邻接链表而言,边在链式前向星中存储的相对顺序与输入顺序相反,其他差异不大。
  • 创建vis数组用于记录节点是否被访问,数组dis1dis2分别记录各点到直径两个端点的距离,num用于记录边的下标,endpoint记录端点(由于两个端点分开计算,可以用同一变量记录)
  • 初始化变量,构造好图后进入代码主体,其包含三次dfs:
  1. 第一次dfs用于找到端点1,可以从任意节点出发(这里取节点1),找到的路径最大的节点即为端点1
  2. 第二次dfs用于找到端点2,从端点2出发,找到的路径最大值即为端点2,这里dfs过程中也同时求出了每个节点i到端点1的距离dis1[i]
  3. 第三次dfs用于求出每个节点i到端点2的距离dis2[i]
  • 对于每个i,dis1[i]和dis2[i]的最大值即为最大长度。

4. 代码

#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;

struct Edge
{
    int vertex, weight, next;
}edges[100111];

bool vis[100111];
int N, num, endpoint;
int head[100111], dis1[100111], dis2[100111];

void addEdge(int u, int v, int w)
{
    edges[num].vertex = v;
    edges[num].weight = w;
    edges[num].next = head[u];
    head[u] = num;
    num++;
}

void dfs(int u, int *dis)
{
    vis[u] = true;
    if (dis[u] > dis[endpoint]) endpoint = u;
    
    for (int i = head[u]; i != -1; i = edges[i].next)
    {
        int v = edges[i].vertex;
        if (!vis[v])
        {
            vis[v] = true;
            dis[v] = dis[u] + edges[i].weight;
            dfs(v, dis);
        }
    }
}

int main()
{
    while (cin >> N)
    {
        num = 0;
        endpoint = 1;
        memset(head, -1, sizeof(head));
        memset(dis1, 0, sizeof(dis1));
        memset(dis2, 0, sizeof(dis2));
        memset(vis, 0, sizeof(vis));
        
        int v, w;
        for (int i = 2; i <= N; i++)
        {
            cin >> v >> w;
            addEdge(i, v, w);
            addEdge(v, i, w);
        }
        
        dfs(1, dis1);
        
        memset(dis1, 0, sizeof(dis1));
        memset(vis, 0, sizeof(vis));
        dfs(endpoint, dis1);
        
        memset(vis, 0, sizeof(vis));
        dfs(endpoint, dis2);
        
        for (int i = 1; i <= N; i++)
            cout << max(dis1[i], dis2[i]) << endl;
    }
    
    return 0;
}

B 带好口罩!

1. 题目大意

新型冠状病毒肺炎(Corona Virus Disease 2019,COVID-19),简称“新冠肺炎”,是指2019新型冠状病毒感染导致的肺炎。
如果一个感染者走入一个群体,那么这个群体需要被隔离!
小A同学被确诊为新冠感染,并且没有戴口罩!!!!!!
危!!!
时间紧迫!!!!
需要尽快找到所有和小A同学直接或者间接接触过的同学,将他们隔离,防止更大范围的扩散。
众所周知,学生的交际可能是分小团体的,一位学生可能同时参与多个小团体内。
请你编写程序解决!戴口罩!!

输入:
多组数据,对于每组测试数据:
第一行为两个整数n和m(n = m = 0表示输入结束,不需要处理),n是学生的数量,m是学生群体的数量(0 < n <= 3e4,0 <= m <= 5e2),学生编号为0~n-1,小A编号为0。
随后m行,每行有一个整数num即小团体人员数量。随后有num个整数代表这个小团体的学生。

输出:
输出要隔离的人数,每组数据的答案输出占一行

样例:

100 4
2 1 2
5 10 13 11 12 14
2 0 1
2 99 2
200 2
1 5
5 1 2 3 4 5
1 0
0 0
4
1
1

2. 思路历程

  • 要求出有多少学生与小A同学接触,即求出多少学生与小A属于同一类,是一个并查集的问题。
  • 并查集是一种用来管理元素分组状况的数据结构。
  1. 可以高效地进行查询元素A和元素B是否属于同一组,以及合并元素A和元素B所在的组两种操作
  2. 可以使用类似于树形的结构实现,但并不在意严格意义上的父子关系或树形状,只关心节点属于哪一个整体
  3. 合并的复杂度为O(1),为了提高查询的复杂度,可以直接把节点挂在跟节点下
  • 通过对所有学生进行并查集操作,把他们那都放入正确的分组,最后小A所在的组元素个数即为与小A接触的学生数。

3. 具体实现

  • 创建数组parent用于记录并查集,i的根结点为parent[i],初始化所有parent[i] = i,表示每个学生自成一组。
  • 函数find(int x)是并查集的查询函数(包含路径紧缩),函数unite(int x, int y)是并查集的合并函数。
  • 对于每个团体,找到团体第一个学生所在的组作为基准组,将该团体中其他学生所在的组与基准组合并(已同组则不操作)
  • 遍历小A所在的组,计算组内元素个数。

4. 代码

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

int n, m, parent[100111];

int find(int x)
{
    if (parent[x] == x)
        return x;
    return parent[x] = find(parent[x]);
}

void unite(int x, int y)
{
    x = find(x);
    y = find(y);
    parent[x] = y;
}

int main()
{
    while (cin >> n >> m)
    {
        if (n == 0 && m == 0) break;
        
        for (int i = 0; i < n; i++) parent[i] = i;
        
        int num, id1, id;
        for (int i = 0; i < m; i++)
        {
            cin >> num >> id1;
            id1 = find(id1);
            for (int j = 1; j < num; j++)
            {
                cin >> id;
                id  = find(id);
                if (id != id1)
                    unite(id1, id);
            }
        }
        
        int ans = 0;
        for (int i = 0; i < n; i++)
        {
            if (find(i) == find(0))
                ans++;
        }
        cout << ans << endl;
    }
    return 0;
}

C 掌握魔法の东东 I

1. 题目大意

东东在老家农村无聊,想种田。农田有 n 块,编号从 1~n。种田要灌溉
众所周知东东是一个魔法师,他可以消耗一定的 MP 在一块田上施展魔法,使得黄河之水天上来。他也可以消耗一定的 MP 在两块田的渠上建立传送门,使得这块田引用那块有水的田的水。 (1<=n<=3e2)
黄河之水天上来的消耗是 Wi,i 是农田编号 (1<=Wi<=1e5)
建立传送门的消耗是 Pij,i、j 是农田编号 (1<= Pij <=1e5, Pij = Pji, Pii =0)
东东为所有的田灌溉的最小消耗。

输入:
第1行:一个数n
第2行到第n+1行:数wi
第n+2行到第2n+1行:矩阵即pij矩阵

输出:
东东最小消耗的MP值

样例:

4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0
9

2. 思路历程

  • 化简题意为,已知每两块田之间通水的消耗和每块田自己通水的消耗,要求整块田通水的最小消耗。
  • 去掉每块田自己通水的消耗,就是最小生成树问题,因此增加超级原点,把自己通水这种情况当成超级原点,则所有n个点都与超级原点相邻且路径有一个权重,然后对n + 1个点进行最小生成树即可。

3. 具体实现

  • 创建结构体Edge,包含当前节点、下一个节点和权重(图的链式前向星存储方式),对其进行操作符重载,用于按权重排序,每次取权重最小的边。
  • 创建数组parent用于记录并查集,i的根结点为parent[i],初始化所有parent[i] = i,表示所有点都还不连通。
  • 函数find(int x)是并查集的查询函数(包含路径紧缩),函数unite(int x, int y)是并查集的合并函数。
  • 构造好图后对边进行排序,然后进行最小生成树算法
    1. 权重由小到大依次考虑所有点,若当前点加入图后不生成闭环则符合要求,将边加入生成树并更新权重和
    2. 当加入图中的边等于 n 时跳出循环(已达到树的边数)

4. 代码

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

struct Edge
{
    int vertex, weight, next;
    bool operator < (const Edge& edge) const
    {
        return weight < edge.weight;
    }
}edges[100011];

int n, parent[100011];
int tot = 0, ans = 0, cnt = 0;

void addEdge(int u, int v, int w)
{
    edges[++tot].vertex = u;
    edges[tot].next = v;
    edges[tot].weight = w;
}

int find(int x)
{
    if (parent[x] == x)
        return x;
    return parent[x] = find(parent[x]);
}

void unite(int x, int y)
{
    x = find(x);
    y = find(y);
    parent[x] = y;
}

int main()
{
    cin >> n;
    int w;
    for (int i = 1; i <= n; i++)
    {
        cin >> w;
        addEdge(i, 0, w);
    }
    
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            cin >> w;
            if (i != j)
                addEdge(i, j, w);
        }
    }
    
    for (int i = 1; i <= n; i++) parent[i] = i;
    
    sort(edges + 1, edges + tot + 1);
    for (int i = 1; i <= tot; i++)
    {
        int v1 = find(edges[i].vertex);
        int v2 = find(edges[i].next);
        if (v1 != v2)
        {
            unite(v1, v2);
            ans += edges[i].weight;
            cnt++;
        }
        if (cnt == n) break;
    }
    
    cout << ans;
    
    return 0;
}

D 数据中心

1. 题目大意

在这里插入图片描述

在这里插入图片描述

样例:

4
5
1
1 2 3
1 3 4
1 4 5
2 3 8
3 4 2
4

2. 思路历程

  • 化简题意为,构造一个连通无向图,使每个节点都与root节点连通,且完成任务所需的时间最少,时间为Tmax = max(Th)。
  • 理解时,把Tmax当作T,h当作边的编号,题意即为求解一棵生成树,使得最大边全最小。
  • 可以用类似最小生成树的方法求解,是最小瓶颈生成树,把原来unite后权重相加改为取最大权重即可。

3. 具体实现

  • 创建结构体Edge,包含当前节点、下一个节点和权重(图的链式前向星存储方式),对其进行操作符重载,用于按权重排序,每次取权重最小的边。
  • 创建数组parent用于记录并查集,i的根结点为parent[i],初始化所有parent[i] = i,表示所有点都还不连通。
  • 函数find(int x)是并查集的查询函数(包含路径紧缩),函数unite(int x, int y)是并查集的合并函数。
  1. 权重由小到大依次考虑所有点,若当前点加入图后不生成闭环则符合要求,将边加入生成树并更新最大权重
    1. 当加入图中的边等于 n - 1 时跳出循环(已达到树的边数)

4. 代码

#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;

struct Edge
{
    int vertex, weight, next;
    bool operator < (const Edge& edge) const
    {
        return weight < edge.weight;
    }
}edges[200022];

int n, m, root, parent[100011];
int tot = 0, ans = 0, cnt = 0;

void addEdge(int u, int v, int w)
{
    edges[++tot].vertex = u;
    edges[tot].next = v;
    edges[tot].weight = w;
}

int find(int x)
{
    if (parent[x] == x)
        return x;
    return parent[x] = find(parent[x]);
}

void unite(int x, int y)
{
    x = find(x);
    y = find(y);
    parent[x] = y;
}

int main()
{
    cin >> n >> m >> root;
    int u, v, w;
    for (int i = 0; i < m; i++)
    {
        cin >> u >> v >> w;
        addEdge(u, v, w);
        addEdge(v, u, w);
    }
    
    for (int i = 0; i <= n; i++) parent[i] = i;
    
    sort(edges + 1, edges + tot + 1);
    for (int i = 1; i <= tot; i++)
    {
        int v1 = find(edges[i].vertex);
        int v2 = find(edges[i].next);
        if (v1 != v2)
        {
            unite(v1, v2);
            ans = max(ans, edges[i].weight);
            cnt++;
        }
        if (cnt == n - 1) break;
    }
    
    cout << ans;
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值