AcWing 346 走廊泼水节

题目描述:

给定一棵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 
#include 
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<

 

你可能感兴趣的:(算法提高课,算法竞赛进阶指南,最小生成树,kruskal)