A - 氪金带东
题目描述
实验室里原先有一台电脑(编号为1),最近氪金带师咕咕东又为实验室购置了N-1台电脑,编号为2到N。每台电脑都用网线连接到一台先前安装的电脑上。但是咕咕东担心网速太慢,他希望知道第i台电脑到其他电脑的最大网线长度,但是可怜的咕咕东在不久前刚刚遭受了宇宙射线的降智打击,请你帮帮他。
提示: 样例输入对应这个图,从这个图中你可以看出,距离1号电脑最远的电脑是4号电脑,他们之间的距离是3。 4号电脑与5号电脑都是距离2号电脑最远的点,故其答案是2。5号电脑距离3号电脑最远,故对于3号电脑来说它的答案是3。同样的我们可以计算出4号电脑和5号电脑的答案是4.
Input
输入文件包含多组测试数据。对于每组测试数据,第一行一个整数N (N<=10000),接下来有N-1行,每一行两个数,对于第i行的两个数,它们表示与i号电脑连接的电脑编号以及它们之间网线的长度。网线的总长度不会超过10^9,每个数之间用一个空格隔开。
Output
对于每组测试数据输出N行,第i行表示i号电脑的答案 (1<=i<=N).
Sample Input
5
1 1
2 1
3 1
1 1
Sample Output
3
2
3
4
4
解题思路
具体思路就是通过三遍BFS得到最终结果;
第一遍BFS:从任意一点出发执行BFS,最终到达的一定是树的直径的两点中的一个,将其记录下来;
第二遍BFS:从记录下来的这一点出发执行BFS,在遍历的过程中记录下来所有的点到这直径上的一点的距离存储在一个数组中,最终的结果则是到达了直径上的另一点,将其记录下来;
第三遍BFS:从记录下的点开始执行BFS,在遍历的过程中记录下来所有的点到这直径上的一点的距离并与数组中的值进行比较,如果大于数组中的值,则将其进行更新;
要注意的是,题目Input中描述了会有多组输入,所以对于输入应该是在while(cin>>n)的环境中,而给的样例是只输了一组数据,所以提交的时候一直WA,读题仔细属实很重要!!!!
解题代码
#include <iostream>
#include <vector>
#include <string>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
const int max_number = 10005;
int N;
int L;
long long int all_length = 0;
struct edge {
int nextNode;
long long int length;
};
vector<edge> w[max_number]; //边集,w[n] 表示序号为n的点与之关联的边的集合
int vis[max_number]; //标记是否到达过该点
long long int max_length1[max_number]; //存储每个点的最大路径(从直径点1开始)
long long int max_length2[max_number]; //从直径点2开始
void bfs1 (int begin) {
vis[begin] = 1;
for (long int i = 0; i < w[begin].size(); i++) {
int t = w[begin][i].nextNode;
if (vis[t] != 1) {
//将距离更新
if (max_length1[t] < max_length1[begin] + w[begin][i].length) {
max_length1[t] = max_length1[begin] + w[begin][i].length;
}
//获取树的最长路的两个叶子结点
if (max_length1[t] > all_length) {
all_length = max_length1[t];
L = t;
}
bfs1(t);
}
}
}
void bfs2 (int begin) {
vis[begin] = 1;
for (long int i = 0; i < w[begin].size(); i++) {
int t = w[begin][i].nextNode;
if (vis[t] != 1) {
//将距离更新
if (max_length2[t] < max_length2[begin] + w[begin][i].length) {
max_length2[t] = max_length2[begin] + w[begin][i].length;
}
bfs2(t);
}
}
}
int main()
{
while (cin>>N) {
int n; long long int l;
//所有网线初始化
for (int a = 0; a < max_number; a++) w[a].clear();
for (int a = 2; a <= N; a++) {
cin>>n>>l;
//边集初始化,每条边都在起点、中点两个地方插入一遍
edge e;
e.nextNode = n; e.length = l; w[a].push_back(e);
e.nextNode = a; w[n].push_back(e);
}
//前两遍BFS 找到树的直径的两个叶子结点
//标记数组初始化 长度数组初始化
memset(vis, 0, sizeof(vis));
memset(max_length1, 0, sizeof(max_length1));
all_length = 0;
bfs1(1);
memset(vis, 0, sizeof(vis));
memset(max_length1, 0, sizeof(max_length1));
all_length = 0;
bfs1(L); //L是第一次bfs之后到达的最远的叶子结点
//第三遍bfs 从直径的另一个叶子结点开始再对距离进行一次更新
memset(vis, 0, sizeof(vis));
memset(max_length2, 0, sizeof(max_length2));
bfs2(L); //L是第二次bfs之后到达的直径的另一个叶子结点
for (int a = 1; a <= N; a++) {
cout<<max(max_length1[a], max_length2[a])<<endl;
}
}
}
B
题目描述
新型冠状病毒肺炎(Corona Virus Disease 2019,COVID-19),简称“新冠肺炎”,是指2019新型冠状病毒感染导致的肺炎。
如果一个感染者走入一个群体,那么这个群体需要被隔离!
小A同学被确诊为新冠感染,并且没有戴口罩!!!!!!
危!!!
时间紧迫!!!!
需要尽快找到所有和小A同学直接或者间接接触过的同学,将他们隔离,防止更大范围的扩散。
众所周知,学生的交际可能是分小团体的,一位学生可能同时参与多个小团体内。
请你编写程序解决!戴口罩!!
Input
多组数据,对于每组测试数据:
第一行为两个整数n和m(n = m = 0表示输入结束,不需要处理),n是学生的数量,m是学生群体的数量。0 < n <= 3e4 , 0 <= m <= 5e2
学生编号为0~n-1
小A编号为0
随后,m行,每行有一个整数num即小团体人员数量。随后有num个整数代表这个小团体的学生。
Output
输出要隔离的人数,每组数据的答案输出占一行
Sample Input
00 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
Sample Output
4
1
1
解题思路
这题是一个典型的用并查集解决的问题;
初始化:每个学生的代表元都是本身;
实现:对于输入的每一个小组,将第一个元素作为这个小组内所有元素的代表元,即用这个元素去代表整个小组即小组内所有元素,这也就相当于是组员都与头领元素发生了交往,也就是题目中的“感染”;
结果:利用并查集,对所有的学生进行查询,发现该元素的代表元为‘0’的话,那么要输出的答案加一,即发生了“感染”;
解题代码
#include <iostream>
#include <vector>
#include <string>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
const int max_number = 30005;
long long int n, m;
long long int student[max_number];
//并查集函数
long long int BCJ (long long int x) {
if (student[x] == x) {return x;}
else
return student[x] = BCJ(student[x]);
}
int main()
{
while (cin>>n>>m) {
if (n == 0 && m == 0) break;
//初始化
for (long long int a = 0; a < max_number; a++) student[a] = a;
for (long long int a = 0; a < m; a++) {
int num; cin>>num;
//第一个元素充当这组所有元素的key
int father; cin>>father;
for (int b = 1; b < num; b++) {
int number; cin>>number;
//可能出现一个元素在多个小组出现的情况,要将各个小组统一
if (BCJ(number) != BCJ(father)) student[BCJ(number)] = BCJ(father);
}
}
int sum = 0;
for (long long int a = 0; a < n; a++) {
if (BCJ(a) == BCJ(0)) sum++;
}
cout<<sum<<endl;
}
}
C
题目描述
东东在老家农村无聊,想种田。农田有 n 块,编号从 1~n。种田要灌氵
众所周知东东是一个魔法师,他可以消耗一定的 MP 在一块田上施展魔法,使得黄河之水天上来。他也可以消耗一定的 MP 在两块田的渠上建立传送门,使得这块田引用那块有水的田的水。 (1<=n<=3e2)
黄河之水天上来的消耗是 Wi,i 是农田编号 (1<=Wi<=1e5)
建立传送门的消耗是 Pij,i、j 是农田编号 (1<= Pij <=1e5, Pij = Pji, Pii =0)
东东为所有的田灌氵的最小消耗
Input
第1行:一个数n
第2行到第n+1行:数wi
第n+2行到第2n+1行:矩阵即pij矩阵
Output
东东最小消耗的MP值
Sample Input
4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0
Sample Output
9
解题思路
实际操作:利用kurskal算法求图中最小生成树
具体:
(1)“黄河之水天上来”,需要我们将一个节点0插入农田构成的图中,进行图重构;
(2)矩阵是一个对称矩阵,那么实际上我们只需要考虑矩阵的一半部分就可以了,然后往边集里面插入;
将边集按照边的权值从小到大进行排列,然后利用并查集将每条选择的边的终点作为起点的代表元,以此为依据判断选择的边加入后是否会成环,只有加入后不成环才能选择;
输出的结果就是所有选择的边的权值的和;
解题代码
#include <iostream>
#include <vector>
#include <string>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
int n;
struct edge {
int firstnode;
int nextnode;
long long int length;
};
bool PX (edge a, edge b) {
return a.length < b.length;
}
vector<edge> t;
int f[100005];
int BCJ (int x) {
if (f[x] == x) return x;
else return f[x] = BCJ(f[x]);
}
int kurskal () {
int number_edge = 0;
int min_MP = 0;
for (long int a = 0; a < t.size(); a++) {
//边加入的条件是加入后不成环
if (BCJ(t[a].firstnode) != BCJ(t[a].nextnode)) {
//加入边,并将它的终点设置为起点的序数
f[BCJ(t[a].firstnode)] = BCJ(t[a].nextnode);
min_MP += t[a].length;
number_edge++;
}
if (number_edge == n) break;
}
return min_MP;
}
int main()
{
cin>>n;
//并查集初始化
for (int a = 0; a <= n; a++) f[a] = a;
//将黄河水作为点‘0’,进行图重构
for (int a = 1; a <= n; a++) {
long long int w; cin>>w;
edge e; e.firstnode = 0; e.nextnode = a; e.length = w;
t.push_back(e);
}
//将农田放入图中
for (int a = 1; a <= n; a++) {
for (int b = 1; b <= n; b++) {
long long int p; cin>>p;
//矩阵是对称的,那么只需要考虑矩阵的一半就可以了
if (b > a) {
edge e; e.firstnode = a; e.nextnode = b; e.length = p;
t.push_back(e);
}
}
}
//按MP值从小到大进行排序
sort(t.begin(), t.end(), PX);
cout<<kurskal()<<endl;
}
D
题目描述
Input
Output
Sample Input
4
5
1
1 2 3
1 3 4
1 4 5
2 3 8
3 4 2
Sample Output
4
解题思路
一开始看了半天题目也不知道在说啥,以为是要求每个点到跟节点的距离,最后看了PPT,再问了同学发现就是求最小生成树里权值最大的一条边;
然后可以借鉴C题的代码,只不过与C题的区别就是在于kruskal返回的最后选择的那条边的在边集里的编号;
结果输出就是根据kruskal的返回值,输出边集里面对应边的权值。
学好语文很重要!!!
解题代码
#include <iostream>
#include <vector>
#include <string>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
int n; //端点数
int m; //边数
int root; //根节点
struct edge {
int firstnode;
int nextnode;
long long int length;
};
bool PX (edge a, edge b) {
return a.length < b.length;
}
vector<edge> t;
int f[50005];
int BCJ (int x) {
if (f[x] == x) return x;
else return f[x] = BCJ(f[x]);
}
long int kurskal () {
int number_edge = 0;
long int max_node = 0;
for (long int a = 0; a < t.size(); a++) {
//边加入的条件是加入后不成环
if (BCJ(t[a].firstnode) != BCJ(t[a].nextnode)) {
//加入边,并将它的终点设置为起点的序数
f[BCJ(t[a].firstnode)] = BCJ(t[a].nextnode);
max_node = a;
number_edge++;
}
if (number_edge == n - 1) break;
}
return max_node;
}
int main()
{
cin>>n>>m>>root;
//并查集初始化
for (int a = 1; a <= n; a++) f[a] = a;
//边集初始化
for (int a = 0; a < m; a++) {
int x, y ,z;
cin>>x>>y>>z;
edge e; e.firstnode = x; e.nextnode = y; e.length = z;
t.push_back(e);
}
//按权值从小到大进行排序
sort(t.begin(), t.end(), PX);
cout<<t[kurskal()].length<<endl;
}