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;
}
posted @ 2024-08-27 08:42  Brilliant11001  阅读(20)  评论(0编辑  收藏  举报