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).
Example Input
5
1 1
2 1
3 1
1 1
Example Output
3
2
3
4
4
题意:
有若干台电脑通过长度不同的网线相连,对于每一台电脑,计算出与其相连的所有电脑中二者之间最长的网线长度。
分析:
首先应该找出树的直径,假设直径两端的点为v1、v2,则对于任意一个点,到其他点的最大长度就是是到点v1或v2的长度。
所以首要任务求出树的直径。对于如何求树的直径,有如下定理:
从任意一个点遍历整棵树,寻找与它距离最远的一片叶子,这个叶子一定是树的直径的一个端点。利用这个方法,连续进行两次dfs,就可以求出树的直径,即找到了点v1、v2。
对于任意一个点,当找到v1、v2后,再进行一次dfs,所求的最大距离就是改点到v1的距离和到v2的距离中的最大值。
代码如下:
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
struct edge
{
int to;
int w;
};
vector<edge> graph[10010];
int ans[10010];
int length, s;
void dfs(int u, int f, int len)
{
if (length <= len)
{
s = u;
length = len;
}
for (int i = 0; i < graph[u].size(); i++)
{
int v = graph[u][i].to;
if (v == f)
continue;
dfs(v, u, len + graph[u][i].w);
ans[v] = max(ans[v], len + graph[u][i].w);
}
}
int main()
{
int n;
edge e;
while (cin >> n)
{
for (int i = 0; i <= n; i++)
graph[i].clear();
for (int i = 2; i <= n; i++)
{
int a, b;
cin >> a >> b;
e.to = a;
e.w = b;
graph[i].push_back(e);
e.to = i;
graph[a].push_back(e);
}
memset(ans, 0, sizeof(ans));
length = 0;
s = 0;
dfs(1, -1, 0);
dfs(s, -1, 0);
dfs(s, -1, 0);
for (int i = 1; i <= n; i++)
cout << ans[i] << endl;
}
return 0;
}
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
输出要隔离的人数,每组数据的答案输出占一行
Example Input
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
Example Output
4
1
1
题意:
有一名同学被确诊,同学之间通过小团体接触,输出需要隔离的同学。
分析:
本题主要使用并查集。
通过函数init、find、unite实现并查集的初始化、查询和合并功能。
将每个群体中的学生合并在同一个并查集中,只需要查询0号学生所在的并查集中共有多少人。
代码如下:
#include<iostream>
#include<cstdio>
using namespace std;
int n, m;
int uni[30010];
int dis[30010];
void init()
{
for (int i = 0; i < n; i++)
{
uni[i] = i;
dis[i] = 1;
}
}
int find(int x)
{
if (uni[x] == x)
return x;
else
return uni[x] = find(uni[x]);
}
void unite(int a, int b)
{
int x = find(a), y = find(b);
if (x == y)
return;
if (dis[x] > dis[y])
swap(x, y);
uni[x] = y;
dis[y] += dis[x];
}
int main()
{
while (scanf("%d%d", &n, &m))
{
if (n == 0 && m == 0)
break;
init();
int a, b, c;
for (int i = 0; i < m; i++)
{
scanf("%d%d", &a, &b);
a -= 1;
while (a--)
{
cin >> c;
unite(b, c);
}
}
printf("%d\n", dis[find(0)]);
}
return 0;
}
C - 掌握魔法の东东 I
东东在老家农村无聊,想种田。农田有 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值
Example Input
4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0
Example Output
9
题意:
要对一块田地上的若干地块进行灌溉,有两种灌溉方法,一种是直接往这一块地浇水,第二种是从旁边的地块引流,每个操作都有不同的损耗,要灌溉所有地块,求出最小的损耗。
分析:
本题核心算法仍然是并查集,但最主要考虑如何调用并查集调用并查集。
本题的灌溉方法有两种,但可以归结为一种,全部看成连通引水。将数据升序排序后利用find函数,每次检查两个最小成本点是否都连通,若没有则利用unite函数进行合并,并将成本加入结果。当所有点全部连通,输出结果。
代码如下:
#include<iostream>
#include<algorithm>
using namespace std;
struct edge
{
int u;
int v;
int w;
edge(int U = 0, int V = 0, int W = 0) :u(U), v(V), w(W) {}
edge(const edge& a) :u(a.u), v(a.v), w(a.w) {}
};
int cmp(edge& a, edge& b)
{
return a.w < b.w;
}
edge e[100010];
int uni[100010] = { 0 };
int dis[100010] = { 0 };
int n, num = 0, ans = 0;
void init()
{
for (int i = 0; i <= n; i++)
{
uni[i] = i;
dis[i] = 1;
}
}
int find(int x)
{
if (x == uni[x])
return x;
else
return uni[x] = find(uni[x]);
}
bool unite(int x, int y)
{
int a = find(x);
int b = find(y);
if (a == b)
return false;
else if (dis[a] < dis[b])
{
dis[b] += dis[a];
uni[a] = b;
}
else
{
dis[a] += dis[b];
uni[b] = a;
}
return true;
}
int main()
{
cin >> n;
init();
for (int i = 1; i < n + 1; i++)
{
int m;
cin >> m;
e[num++] = edge(0, i, m);
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
int mp;
cin >> mp;
if (mp == 0)
continue;
e[num++] = edge(i, j, mp);
}
}
sort(e, e + num, cmp);
for (int i = 0; i < num; i++)
{
int u = e[i].u;
int v = e[i].v;
int m = e[i].w;
if (unite(u, v))
ans += m;
}
cout << ans;
}
D - 数据中心
Example Input
4
5
1
1 2 3
1 3 4
1 4 5
2 3 8
3 4 2
Example Output
4
Notes
题意:
有一个无向图,图中有一个根节点,每两个节点之间传输信息都需要一定的时间,现在每个节点都要向根节点传输一条信息,求出最短用时。
分析:
说到底,这道题的最终要求是先求出该图的最小生成树,然后以root为根节点每一层向下遍历即可。
先将给出的数据从小到大排序,然后开始建树,使用数组标记是否到达过某一个点,这里使用了kruskal算法。
从根节点开始层次遍历,定义变量将权重相加,最后结果输出。
代码如下:
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
struct edge
{
int start;
int to;
int val;
};
edge e[500000];
int father[100000];
int n, m, u, v, total;
int ans = 0;
int find(int x)
{
if (father[x] == x)
return x;
else
return father[x] = find(father[x]);
}
int cmp(edge a, edge b)
{
return a.val < b.val;
}
void kruskal()
{
long long num = 0;
for (int i = 1; i <= m; i++)
{
u = find(e[i].start);
v = find(e[i].to);
if (u == v)
continue;
num += e[i].val;
ans = max(ans, e[i].val);
father[u] = v;
total++;
if (total == n - 1)
break;
}
}
int main()
{
cin >> n >> m;
int root;
cin >> root;
for (int i = 1; i <= n; i++)
father[i] = i;
for (int i = 1; i <= m; i++)
cin >> e[i].start >> e[i].to >> e[i].val;
sort(e + 1, e + 1 + m, cmp);
kruskal();
cout << ans;
return 0;
}