P10928 走廊泼水节
题目描述
给定一棵 NNN 个节点的树,要求增加若干条边,把这棵树扩充为完全图,并满足图的唯一最小生成树仍然是这棵树。
求增加的边的权值总和最小是多少。
注意: 树中的所有边权均为整数,且新加的所有边权也必须为整数。
输入格式
第一行包含整数 ttt,表示共有 ttt 组测试数据。
对于每组测试数据,第一行包含整数 NNN。
接下来 N−1N-1N−1 行,每行三个整数 X,Y,ZX,Y,ZX,Y,Z,表示 XXX 节点与 YYY 节点之间存在一条边,长度为 ZZZ。
输出格式
每组数据输出一个整数,表示权值总和最小值。
每个结果占一行。
输入输出样例 #1
输入 #1
2
3
1 2 2
1 3 3
4
1 2 3
2 3 4
3 4 5
输出 #1
4
17
说明/提示
数据保证,1≤t≤101\leq t\leq 101≤t≤10,1≤N≤60001 \le N \le 60001≤N≤6000,1≤Z≤1001 \le Z \le 1001≤Z≤100。
对于这题,要令其变为完全图后MST不变,那么新增的每一条边都必须比当前路径最小边权大。考虑到复杂度,我们选用kruskal。我们需要维护一个size数组,用于记录每个连通分量的大小。在合并联通分量A、B时,我们将新增size[A]*size[B]-1条边,这些边的边权则等于当前选择的最短边+1。
#include<bits/stdc++.h>
using namespace std;
int find_(vector<int>& p, int x) {
if (p[x] == x) {
return p[x];
}
return p[x] = find_(p, p[x]);
}
int main() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<tuple<int, int, int>> e;
for (int i = 0; i < n - 1; i++) {
int x, y, z;
cin >> x >> y >> z;
e.emplace_back(z, x, y);
}
sort(e.begin(), e.end());
vector<int> p(n + 1);
iota(p.begin() + 1, p.end(), 1);
vector<long long> sz(n + 1, 1);
long long total_sum = 0;
for (const auto& edge : e) {
auto [w, u, v] = edge;
int root_u = find_(p, u);
int root_v = find_(p, v);
if (root_u != root_v) {
long long size_u = sz[root_u];
long long size_v = sz[root_v];
total_sum += (size_u * size_v - 1) * (w + 1);
p[root_v] = root_u;
sz[root_u] += sz[root_v];
}
}
cout << total_sum << endl;
}
return 0;
}