346 走廊泼水节(kruskal扩展)

1. 问题描述:

给定一棵 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
来源: https://www.acwing.com/problem/content/348/

2. 思路分析:

完全图其实分为有向完全图与无向完全图,对于无向完全图来说每对顶点之间都有一条边相连,对于有向完全图来说每对顶点之间都有两条有向边相互连接。这道题目其实是kruskal中很经典的问题,如果之前没有做过是很难想出来的,我们可以想到的是:一开始的时候是一棵树可以看成树中的每一个节点都是孤立的,每一次随意挑选两个集合进行合并,在合并之前两个集合是完全图集合,所以我们可以只需要将一个集合中的每一个点向另外一个集合中的每一个点连一条边那么合并之后的点集也是一个完全图集合,对于每一个集合都是这样合并最终树中的所有节点就可以扩充成一个完全图集合,核心是按照什么顺序进行合并可以得到最小值,其实我们可以按照边权由小到大进行排序,按照边从小到大的顺序合并每两个集合,例如两个集合分别为A,B,A集合中的每一个点向B集合中的每一个点连一条边,若当前联通A,B集合边的信息为(a,b,w),则合并AB集合连的每一条边与w存在什么关系呢?也即需要满足什么条件,存在这三种关系:

  • 新加的边 < w
  • 新加的边 = w
  • 新加的边 > w

对于第一种情况可以发现是不满足题目原来的生成树还是新生成的完全图的最小生成树,对于第二种情况,如果相等说明最小生成树是不唯一的,所以只能满足第三种情况,也即新加的边的权重大于等于w + 1,这相当于是求解出了每一条边的长度下限,每一条新加的边都是w + 1,所以最终加的边权之和肯定是最小的,而且最小生成树还是当前这一棵树,但是这棵最小生成树是否是唯一的呢?可以发现是唯一的,可以使用反证法来证明,因为当前两个未联通的集合所加新边的权重都是大于等于w + 1的所以如果之前树中对应的联通这两个集合的旧边换成新边那么权重之和是变大的所以还不如之前的旧边,所以最小生成树是唯一的。

3. 代码如下:

from typing import List


class Solution:
    # 并查集查找x的父节点与路径压缩
    def find(self, x: int, fa: List[int]):
        if x != fa[x]:
            fa[x] = self.find(fa[x], fa)
        return fa[x]

    def process(self):
        # 表示T组测试数据
        T = int(input())
        while T > 0:
            T -= 1
            n = int(input())
            w = list()
            # 因为是树所以一定是n - 1条边
            for i in range(n - 1):
                a, b, c = map(int, input().split())
                w.append((a, b, c))
            # kruskal过程, x[2]表示按照元组中第三个元素也即按照边权由小到大排序
            w.sort(key=lambda x: x[2])
            # 父节点列表
            fa = [i for i in range(n + 1)]
            # 记录每一个集合中的节点个数, 因为在合并的时候需要知道每一个集合中点的数目所以使用size来维护集合中父节点所代表的的集合的点的数目, 维护父节点代表的集合的点数即可
            size = [0] * (n + 1)
            for i in range(1, n + 1):
                fa[i] = i
                # 需要初始化size, 因为每一个点相当于是一个集合
                size[i] = 1
            res = 0
            for i in range(n - 1):
                a, b = self.find(w[i][0], fa), self.find(w[i][1], fa)
                if a != b:
                    fa[a] = b
                    # 因为本身就有一条边所以需要减去1, 集合的完全图中的每一个点向另外一个集合中连一条边所以应该是(x * y - 1)
                    res += (size[b] * size[a] - 1) * (w[i][2] + 1)
                    # 因为需要维护集合中点的数量所以使用size维护, 并且a父节点是b, 所以合并之后祖先节点中size位置的个数就是合并之后的集合中点的数目, 所以是size[b]加上size[a]
                    size[b] += size[a]
            print(res)


if __name__ == "__main__":
    Solution().process()

猜你喜欢

转载自blog.csdn.net/qq_39445165/article/details/121115386