1145 北极通讯网络(kruskal算法)

1. 问题描述:

北极的某区域共有 n 座村庄,每座村庄的坐标用一对整数 (x,y) 表示。为了加强联系,决定在村庄之间建立通讯网络,使每两座村庄之间都可以直接或间接通讯。通讯工具可以是无线电收发机,也可以是卫星设备。无线电收发机有多种不同型号,不同型号的无线电收发机有一个不同的参数 d,两座村庄之间的距离如果不超过 d,就可以用该型号的无线电收发机直接通讯,d 值越大的型号价格越贵。现在要先选择某一种型号的无线电收发机,然后统一给所有村庄配备,数量不限,但型号都是 相同的。配备卫星设备的两座村庄无论相距多远都可以直接通讯,但卫星设备是有限的,只能给一部分村庄配备。现在有 k 台卫星设备,请你编一个程序,计算出应该如何分配这 k 台卫星设备,才能使所配备的无线电收发机的 d 值最小。
例如,对于下面三座村庄:


其中,|AB|=10,|BC|=20,|AC|=105√≈22.36。如果没有任何卫星设备或只有 1 台卫星设备 (k=0 或 k=1),则满足条件的最小的 d=20,因为 A 和 B,B 和 C 可以用无线电直接通讯;而 A 和 C 可以用 B 中转实现间接通讯 (即消息从 A 传到 B,再从 B 传到 C);如果有 2 台卫星设备 (k=2),则可以把这两台设备分别分配给 B 和 C ,这样最小的 d 可取 10,因为 A 和 B 之间可以用无线电直接通讯;B 和 C 之间可以用卫星直接通讯;A 和 C 可以用 B 中转实现间接通讯。如果有 3 台卫星设备,则 A,B,C 两两之间都可以直接用卫星通讯,最小的 d 可取 0。

输入格式

第一行为由空格隔开的两个整数 n,k;接下来 n 行,每行两个整数,第 i 行的 xi,yi 表示第 i 座村庄的坐标 (xi,yi)。

输出格式

一个实数,表示最小的 d 值,结果保留 2 位小数。

数据范围

1 ≤ n ≤ 500,
0 ≤ x,y ≤ 10 ^ 4,
0 ≤ k ≤ 100

输入样例:

3 2
10 10
10 0
30 0

输出样例:

10.00

来源:https://www.acwing.com/problem/content/description/1147/

2. 思路分析:

根据题目中的关键字"直接或者间接联通"可以判断出应该是最小生成树问题,对于这种比较复杂的题目首先需要理清楚其中的逻辑关系,先抽象一下为具体的模型,假设d值已经确定了所有小于等于d的村庄可以直接进行通信,此时就会形成若干个连通块,有多个连通块那么就需要多少个卫星来使得这些连通块联通使其任意两个村庄可以直接或者间接通信,所以我们需要找一个最小的d使得将所有权值大于d的边删除之后的联通块的个数小于等于k,我们回忆一下kruskal算法的流程,可以发现kruskal算法从小到大扫描所有边,依次将没有合并的点集合并,所以本质上求解的是连通性的问题,假设当前已经循环到第i条边了,那么肯定是求解出了前i - 1条边构成的连通块,可以参照下图,所以我们在使用kruskal算法的时候找到第一次小于等于k的点对应的d值即可。

3. 代码如下:

from typing import List
import math


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 getDis(self, a: tuple, b: tuple):
        dx = a[0] - b[0]
        dy = a[1] - b[1]
        return math.sqrt(dx * dx + dy * dy)

    def process(self):
        n, k = map(int, input().split())
        q = list()
        for i in range(n):
            x, y = map(int, input().split())
            # 使用元组来存储坐标
            q.append((x, y))
        w = list()
        for i in range(n):
            # 因为是无向图所以只求解一半即可
            for j in range(i):
                # kruskal存储边的三元组
                w.append((i, j, self.getDis(q[i], q[j])))
        # 按照边权由小到大排序
        w.sort(key=lambda x: x[2])
        fa = [i for i in range(n)]
        count, res = n, 0.0
        for i in range(len(w)):
            if count <= k: break
            a, b = self.find(w[i][0], fa), self.find(w[i][1], fa)
            # a与b不属于同一个集合所以需要合并, 合并之后集合的数量减1
            if a != b:
                count -= 1
                res = w[i][2]
                fa[a] = b
        # 保留两位有效数字
        return "{:.2f}".format(res)


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

おすすめ

転載: blog.csdn.net/qq_39445165/article/details/121085112