题目描述:
给定一棵N个节点的树,要求增加若干条边,把这棵树扩充为完全图,并满足图的唯一最小生成树仍然是这棵树。
求增加的边的权值总和最小是多少。
注意: 树中的所有边权均为整数,且新加的所有边权也必须为整数。
输入格式
第一行包含整数t,表示共有t组测试数据。
对于每组测试数据,第一行包含整数N。
接下来N-1行,每行三个整数X,Y,Z,表示X节点与Y节点之间存在一条边,长度为Z。
输出格式
每组数据输出一个整数,表示权值总和最小值。
每个结果占一行。
数据范围
1≤N≤6000
1≤Z≤100
输入样例:
2
3
1 2 2
1 3 3
4
1 2 3
2 3 4
3 4 5
输出样例:
4
17
分析:
一般求最小生成树的问题是给定一张图,让我们选出n - 1条边让图中的n个点连通起来。而本题是给定一个生成树,让我们加边变成完全图后,这个完全图的唯一的最小生成树仍是开始的那棵树。虽然题目描述的过程相当于求最小生成树的逆过程,但是我们依旧可以用最小生成树的方式去求解。
假设有一个完全图,我们在用kruskal算法求解其最小生成树时,选中的n-1条边一定会是题目中给的生成树上的边,而且是按照边权从小到大逐个添加到生成树里面的,所以我们可以将生成树中的边排下序,还原下kruskal算法的过程。设此时有x到y构成的一条边,边权是w,并且fa[x] = a,fa[y] = b,a != b。说明了a和b不在同一个集合里,所以我们将a和b合并,这是第一次a集合和b集合发生合并,说明了在完全图中a集合到b集合中不存在小于w的边,也没有等于w的边,否则生成树就不唯一了。原来a集合中有cnt[a]个节点,b集合中有cnt[b]个节点,我们要合并a、b集合,有cnt[a] * cnt[b]条边可以选择,但是只有此时的边权为w的边是我们最终选择的,既然是完全图,说明剩下的边也都存在,并且边权大于w,为了使得加边的权值之和最小,其他边的边权就取作w + 1了,所以在合并a和b集合时,相当于增加了(cnt[a] * cnt[b] - 1) * (w + 1)的权值。将kruskal算法的流程走一遍,就求出了我们所加边权之和的最小值了。总的代码如下:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 6005;
int fa[N],cnt[N];
struct edge{
int a,b,w;
bool operator < (const edge& ed)const{
return w < ed.w;
}
}e[N];
int find(int x){
if(x != fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
int main(){
int T,n,x,y,z;
cin>>T;
while(T--){
cin>>n;
for(int i = 0;i < n - 1;i++){
cin>>x>>y>>z;
e[i] = {x,y,z};
}
sort(e,e + n - 1);
for(int i = 1;i <= n;i++){
fa[i] = i;
cnt[i] = 1;
}
int res = 0;
for(int i = 0;i < n - 1;i++){
int a = find(e[i].a),b = find(e[i].b),w = e[i].w;
if(a != b){
fa[a] = b;
res += (cnt[a] * cnt[b] - 1) * (w + 1);
cnt[b] += cnt[a];
}
}
cout<<res<<endl;
}
return 0;
}