P10928 走廊泼水节 题解
题目大意:
给定一棵 \(n\) 个节点的树,现在要添加一些边,使它成为一个完全图,并且满足图的唯一最小生成树仍然是原树,求增加的边的最小边权和。
思路:
考虑 kruskal 算法的过程:将边从小到大排序,依次扫描每条边,然后考虑合并边两端的集合。
设这两端的集合为 \(S_x,S_y\),这条边的边权为 \(w\),因为要连成一个完全图,所以这两个集合之间任意一个点对都要连边,其中也包括这条树边。
根据最小生成树的定义可知,我们应该将其他的边都钦定为 \(w + 1\),否则就会存在一条边可以替换这条树边,使得的最小生成树不唯一。
综上所述,我们在这棵树上执行类似 kruskal 算法的过程,先将边按从小到大排序,再依次扫描每条边,并查集维护,若两端的集合未被合并,根据乘法原理,应该连的边权和为 \((size_x\cdot size_y - 1)\cdot (w + 1)\),然后再将两个集合合并即可。
时间复杂度为 \(O(n\log n)\)。
\(\texttt{Code:}\)
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 6010;
int T, n;
struct node{
int a, b, w;
bool operator <(const node &o) const {
return w < o.w;
}
}edges[N];
int p[N], sz[N];
int res;
int find(int x) {
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main() {
scanf("%d", &T);
while(T--) {
scanf("%d", &n);
int a, b, c;
for(int i = 1; i < n; i++) {
scanf("%d%d%d", &a, &b, &c);
edges[i] = {a, b, c};
}
sort(edges + 1, edges + n);
for(int i = 1; i <= n; i++) p[i] = i, sz[i] = 1;
res = 0;
for(int i = 1; i < n; i++) {
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
a = find(a), b = find(b);
if(a != b) {
res += (w + 1) * (sz[a] * sz[b] - 1);
p[a] = b;
sz[b] += sz[a];
}
}
printf("%d\n", res);
}
return 0;
}