题目链接:


这道题卡了我比较久,主要是第一次学习了并查集这种算法,以及这道题的处理方式
一、问题化简
把每台计算机看作点,每条仍能工作的网线看作无向边。则"通过残存网线或跳线直接或间接连通"即图上的连通。设当前图由 C个连通块组成。
- 每条应急跳线连接两台计算机,即一条新边。
- 目标一:用最少的边使全图连通。
- 目标二:在边数最少的前提下,最小化"单个点上关联的跳线条数"的最大值。
二、目标一:最少跳线数
C个连通块要并成一片,每加一条边最多使连通块数减一,因此至少需要 C−1条边。
另一方面,把 C个连通块依次串成一条链,相邻两块之间连一条跳线,恰好用 C−1条即可全部连通。
故第一问答案为:
ans1=C−1
特别地,当 C=1 时答案均为 0。
三、目标二:均衡分摊
关键是把计数对象从"连通块"换成"线头"。
- 共需 C−1 条跳线;
- 每条跳线有两个端点,因此共有 2(C−1)个线头;
- 线头必须落在具体的计算机上。
由于每个连通块内部已经连通,块内任意一台计算机都可以作为接线点。因此全部 NN 台计算机都是可用插口。
于是问题转化为:
把 K=2(C−1)个线头分配到 N 台计算机上,最小化单台承担数的最大值。
设单台上限为 t,则 NN 台最多容纳 Nt个线头。要容纳全部线头,须:

取满足该式的最小整数:

需要注意,这个值只是下界;只有当它确实可以通过某种布线方案取到时,才是答案。可以构造证明它可达:将各连通块按"内部可用机器数"排布成一棵树(机器数多的块置于内部、只有一台机器的块置于叶位置),再把块所需的线头按其内部机器轮流分配,即不会超过该上限。
代码:
cpp
#include <bits/stdc++.h>
using namespace std;
int n, m;
vector<int> fa; // 并查集:fa[x] 表示 x 的父结点
// 查找 x 所在集合的代表元,同时进行路径压缩
int find(int x) {
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
// 合并 x、y 所在的两个集合
void unite(int a, int b) {
a = find(a);
b = find(b);
if (a == b) return; // 已属同一集合,无需合并
fa[a] = b; // 将一个代表元挂到另一个代表元之下
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
// 1. 并查集初始化:每个点单独成为一个集合
fa.resize(n + 1);
for (int i = 1; i <= n; ++i) fa[i] = i;
// 2. 读入残存网线并合并
for (int i = 0; i < m; ++i) {
int u, v;
cin >> u >> v;
unite(u, v);
}
// 3. 统计连通块个数:代表元即满足 fa[i] == i 的点
int comp = 0;
for (int i = 1; i <= n; ++i)
if (fa[i] == i) ++comp;
// 4. 已全连通,无需布线
if (comp == 1) {
cout << "0 0\n";
return 0;
}
// 5. 第一问:C 个连通块至少需要 C-1 条边
long long ans1 = comp - 1;
// 6. 第二问:每条边两个端点,共 2*(C-1) 个线头,分摊到 n 台机器上
// 对 n 向上取整,得到单台承担数的下界(且该下界可达到)
long long heads = 2 * ans1;
long long ans2 = (heads + n - 1) / n; // 向上取整的整数写法
cout << ans1 << ' ' << ans2 << '\n';
return 0;
}