A - 氪金带东
题意:
实验室里原先有一台电脑(编号为1),最近氪金带师咕咕东又为实验室购置了N-1台电脑,编号为2到N。每台电脑都用网线连接到一台先前安装的电脑上。但是咕咕东担心网速太慢,他希望知道第i台电脑到其他电脑的最大网线长度,但是可怜的咕咕东在不久前刚刚遭受了宇宙射线的降智打击,请你帮帮他。
思路:
该题是一个树结构,我们需要找到每一个点到其他点的最大距离。可以证明的是,树上一个点距离最远的点肯定会落在树的直径的一端。因此这个题需要用到树的直径。这个题大致分三步:
- 利用bfs,从任意一个点出发,求出树的直径的一个端点。
- 利用bfs,从已求得的树的直径的端点,求出树的直径的另一个端点
- 利用bfs,求出其他点与树的直径端点间的距离最大值,得到结果。
需要注意的是,如果直接从其他点求直径端点距离,需要很多次bfs。所以不如从树的直径端点求到其他点的距离,三步总的bfs次数只有三次。
总结:
刚开始是用的dfs的方法,因为递归的原因,无论是求距离还是求直径端点都贼麻烦,之后换成了bfs,真香。
代码:
#include <iostream>
#include <algorithm>
#include <queue>
#include <string.h>
using namespace std;
const int N = 1e5 + 1;
struct Edge
{
int u, v, w, nxt;
} Edges[2*N];
int head[N], tot, max1, dis[N], vis[N], dis2[N]; //tot是Edge的下标
int d;
inline void init(int n)
{
tot = 0;
for (int i = 0; i < n; i++)
head[i] = 0;
}
inline void addEdge(int u, int v, int w)
{
tot++;
Edges[tot].u = u;
Edges[tot].v = v;
Edges[tot].w = w;
Edges[tot].nxt = head[u];
head[u] = tot;
}
/*
int dfs(int u)
{
for (int i = head[u]; i != -1; i = Edges[i].nxt)
{
if (!vis[Edges[i].v])
{
vis[Edges[i].v] = true;
int max1 = dfs(Edges[i].v);
}
dis[Edges[i].v] = dis[temp] + Edges[i].w;
if (max1 < dis[Edges[i].v])
{
max1 = dis[Edges[i].v];
d = Edges[i].v;
}
}
return max1;
}
*/
//该题比较适合使用bfs
//模拟正向过程
inline void bfs(int u)
{
queue<int> q;
d = u;
max1 = 0;
memset(dis, 0, sizeof(dis));
memset(vis, 0, sizeof(vis));
vis[u] = true;
q.push(u);
while (!q.empty())
{
int temp = q.front();
q.pop();
for (int i = head[temp]; i; i = Edges[i].nxt)
{
if (!vis[Edges[i].v])
{
dis[Edges[i].v] = dis[temp] + Edges[i].w;
vis[Edges[i].v] = true;
q.push(Edges[i].v);
if (max1 < dis[Edges[i].v])
{
max1 = dis[Edges[i].v];
d = Edges[i].v;
}
}
}
}
}
int main()
{
int n;
while (scanf("%d", &n)==1)
{
tot = 0;
for (int i = 1; i <= n; i++)
head[i] = 0;
for (int i = 2; i <= n; i++)
{
int v, w;
scanf("%d%d", &v, &w);
addEdge(i, v, w);
addEdge(v, i, w);
}
bfs(1);
//从一个最远端点开始bfs
bfs(d);
for (int i = 1; i <= n; i++)
dis2[i] = dis[i];
//从另一个最远端点开始bfs
bfs(d);
for (int i = 1; i <= n; i++)
printf("%d\n", max(dis2[i], dis[i]));
}
}
B - 戴好口罩!
题意:
新型冠状病毒肺炎(Corona Virus Disease 2019,COVID-19),简称“新冠肺炎”,是指2019新型冠状病毒感染导致的肺炎。
如果一个感染者走入一个群体,那么这个群体需要被隔离!
小A同学被确诊为新冠感染,并且没有戴口罩!!!!!!
危!!!
时间紧迫!!!!
需要尽快找到所有和小A同学直接或者间接接触过的同学,将他们隔离,防止更大范围的扩散。
众所周知,学生的交际可能是分小团体的,一位学生可能同时参与多个小团体内。
请你编写程序解决!戴口罩!!
思路:
很明显,这个题就是求有共同联系的集合,所以需要用到并查集,求出0号元素所在集合的所有元素。
并查集主要有查找,合并两种操作。
- 查找操作需要用到路径压缩。
- 合并的时候要把小树合并到大树上,需要用一个rank数组记录集合大小。
总结:
并查集的写法一定要熟练掌握。
代码:
#include <iostream>
#include <algorithm>
#include <string.h>
#include <stdio.h>
using namespace std;
const int M = 5e2 + 1;
const int N = 3e4 + 1;
int par[N], ran[N];
void init(int n)
{
for (int i = 0; i < n; i++)
par[i] = i, ran[i] = 1;
}
int find(int x)
{
return (par[x] == x) ? x : par[x] = find(par[x]);
}
bool unite(int x, int y)
{
x = find(x);
y = find(y);
if (x == y)
return false;
if (ran[x] > ran[y])
swap(x, y);
par[x] = y, ran[y] = (ran[x] += ran[y]);
return true;
}
int main()
{
int n, m;
while (scanf("%d%d", &n, &m) == 2 && !(n == 0 && m == 0))
{
init(n);
for (int i = 0; i < m; i++)
{
int num, last = -1;
scanf("%d", &num);
for (int j = 0; j < num; j++)
{
int x;
scanf("%d", &x);
if (last != -1)
unite(x, last);
last = x;
}
}
printf("%d\n", ran[find(0)]);
}
//system("pause");
}
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)
东东为所有的田灌溉的最小消耗
思路:
为了最小的消耗,实际上就是求 水源+农田的图中的最小生成树。将水源点当作0号节点,求解最小生成树即可。
总结:
求解最小生成树的代码一定要掌握,已经达到标准的时候就要停止。
代码:
#include <iostream>
#include <algorithm>
#include <string.h>
#include <stdio.h>
using namespace std;
const int N = 1e5 + 10;
int par[N], ran[N], tot, n;
struct Edge
{
int u, v, w, nxt;
bool operator<(const Edge a)
{
return w < a.w;
}
} Edges[2 * N];
void init(int n)
{
for (int i = 0; i <= n; i++)
par[i] = i, ran[i] = 1;
}
int find(int x)
{
return (par[x] == x) ? x : par[x] = find(par[x]);
}
bool unite(int x, int y)
{
x = find(x);
y = find(y);
if (x == y)
return false;
if (ran[x] > ran[y])
swap(x, y);
par[x] = y, ran[y] = (ran[x] += ran[y]);
return true;
}
int kruskal()
{
init(n);
sort(Edges, Edges + tot);
int cnt = 0, ans = 0;
for (int i = 0; i < tot; i++)
{
if (unite(Edges[i].u, Edges[i].v)) //合法
{
ans += Edges[i].w;
if (++cnt == n)
return ans;
}
}
return -1;
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &Edges[tot].w), Edges[tot].u = 0, Edges[tot].v = i, tot++;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
scanf("%d", &Edges[tot].w);
if (i != j)
Edges[tot].u = i, Edges[tot].v = j, tot++;
}
}
printf("%d", kruskal());
//system("pause");
}
D - 数据中心
题意:
思路:
题目很长,影响了读题。仔细读题后可以知道,该题就是要对一个加权无向图,求解一个最大边权最小的树结构,实际上就是最大值最小问题。
对于本题,利用最小生成树即可求解。因为kruskal开始将边的权值从小到大排了一次序,最后一个加入最小生成树的边一定是最大值最小的。
总结:
仔细读题,好好利用kruskal。这题学长不讲我绝对不知道咋做。。。。
代码:
#include <iostream>
#include <algorithm>
#include <string.h>
#include <stdio.h>
using namespace std;
const int N = 1e5 + 10;
int par[N], ran[N], tot, n, m, root;
struct Edge
{
int u, v, w, nxt;
bool operator<(const Edge a)
{
return w < a.w;
}
} Edges[2 * N];
void init(int n)
{
for (int i = 0; i < n; i++)
par[i] = i, ran[i] = 1;
}
int find(int x)
{
return (par[x] == x) ? x : par[x] = find(par[x]);
}
bool unite(int x, int y)
{
x = find(x);
y = find(y);
if (x == y)
return false;
if (ran[x] > ran[y])
swap(x, y);
par[x] = y, ran[y] = (ran[x] += ran[y]);
return true;
}
int kruskalMax()
{
init(n);
sort(Edges, Edges + tot);
int cnt = 0, ans = 0;
for (int i = 0; i < tot; i++)
{
if (unite(Edges[i].u, Edges[i].v)) //合法
{
ans = max(ans, Edges[i].w);
if (++cnt == n - 1)
return ans;
}
}
return -1;
}
int main()
{
scanf("%d%d%d", &n, &m, &root);
init(n);
for (int i = 0; i < m; i++)
scanf("%d%d%d", &Edges[tot].u, &Edges[tot].v, &Edges[tot].w), tot++;
printf("%d\n", kruskalMax());
//system("pause");
}
总结:
这周主要复习了bfs,dfs,并查集,kruskal算法。在此基础上,学习了如何利用上述方法求树的直径。
重点:
-
会熟练的写bfs,dfs,并查集,kruskal算法的代码
-
看到复杂的题时,能够知道自己需要使用什么算法。