📌  相关文章
📜  国际空间研究组织 | ISRO CS 2007 |问题 71(1)

📅  最后修改于: 2023-12-03 14:50:46.002000             🧑  作者: Mango

国际空间研究组织 | ISRO CS 2007 |问题 71

这是关于国际空间研究组织(ISRO)在2007年组织的CS(Civil Service)考试的第71题。CS考试是ISRO用来招聘程序员和其他职位的考试之一。

题目描述

给定一个带权的、无向的联通图。权重表示每条边的长度。从任意一个顶点出发,到达另外一个顶点的路径称为 "通路"。通路的长度为路径上所有边的权重之和。最小生成树是一张图的生成树中,边权之和最小的生成树。请问,最小生成树的边权之和可能等于哪些值?

输入格式

第一行包含一个整数 $T$,表示测试数据的组数。

对于每组测试数据:

第一行包含两个整数 $N$ 和 $M$,表示图的节点数和边数。

接下来 $M$ 行,每行三个整数 $x$,$y$ 和 $w$,表示节点 $x$ 和节点 $y$ 之间有一条无向边,边权为 $w$。

输出格式

对于每组测试数据,输出一行,包含所有可能的最小生成树的边权之和,按从小到大排序,并用空格隔开。如果不存在最小生成树,则在一行中输出 "NO"。

样例输入
2
3 3
1 2 1
2 3 2
1 3 3
3 3
1 2 1
2 3 2
3 1 3
样例输出
3 4
NO
题解

这是一道关于最小生成树的寻找的问题。最小生成树可以用Kruskal算法或Prim算法寻找。如果最小生成树存在,则其边权之和必定唯一,因此可以用Kruskal算法或Prim算法求出最小生成树,并输出其边权之和。如果最小生成树不存在,则无法求出其边权之和。

请注意,如果有一条边的权重相同则可能存在多个边权值相同的最小生成树。

要求出所有可能的最小生成树的边权值之和,需要按照如下步骤:

  1. 使用Kruskal或Prim算法求出其中一个最小生成树$T$。
  2. 对于任意$T$中的一条边 $e$,删除它,重新运行Kruskal或Prim算法,求出另一个最小生成树$T'$。
  3. 比较$T$和$T'$的边权之和 $sum(T)$ 和 $sum(T')$。如果它们相等,则这两个最小生成树都满足条件,将它们的边权之和存入结果列表中。
  4. 重复步骤2和3直到删除$T$中的所有边。

由于此题要求输出所有可能的最小生成树的边权之和,而不是求最小生成树的边权之和,因此最后要将结果排序,以便按升序输出。

代码实现
import heapq

def prim(graph):
    nodes = set(graph.keys())  # 节点集合
    visited = set()  # 已访问的节点集合
    heap = []  # 用于保存边的最小堆
    root = None  # 起点
    mst = []  # 最小生成树的边集合
    for node in nodes:
        root = node  # 任意选一个点作为起点
        break
    visited.add(root)
    # 将从起点开始的所有边加入最小堆
    for neighbor, weight in graph[root]:
        heapq.heappush(heap, (weight, root, neighbor))
    while heap and nodes != visited:
        weight, frm, to = heapq.heappop(heap)
        if to not in visited:
            visited.add(to)
            mst.append((frm, to, weight))
            # 将新添加的节点的邻接边加入最小堆
            for neighbor, weight in graph[to]:
                heapq.heappush(heap, (weight, to, neighbor))
    # 如果最小生成树包含所有节点,则返回边集合
    if nodes == set([x[0] for x in mst] + [x[1] for x in mst]):
        return mst
    # 如果最小生成树不包含所有节点,则返回NO
    return "NO"

def kruskal(edges, nodes):
    sorted_edges = sorted(edges, key=lambda x: x[2])  # 按权重排序
    parent = {node: node for node in nodes}  # 初始化并查集
    mst = []  # 最小生成树的边集合
    for edge in sorted_edges:
        frm, to, weight = edge
        group1 = parent[frm]  # 查找edge的两个顶点所在的组
        group2 = parent[to]
        if group1 != group2:  # 如果不在同一组,则将edge加入最小生成树
            mst.append(edge)
            # 将group1和group2并为一组
            for key, value in parent.items():
                if value == group2:
                    parent[key] = group1
    # 如果最小生成树包含所有节点,则返回边集合
    if set(parent.values()) == set([parent[node] for node in nodes]):
        return mst
    # 如果最小生成树不包含所有节点,则返回NO
    return "NO"

for _ in range(int(input())):
    n, m = map(int, input().split())
    graph = {}
    for i in range(m):
        x, y, w = map(int, input().split())
        if x not in graph:
            graph[x] = []
        if y not in graph:
            graph[y] = []
        graph[x].append((y, w))
        graph[y].append((x, w))
    mst1 = prim(graph)
    if mst1 == "NO":
        print("NO")
        continue
    edges = [(min(u, v), max(u, v), w) for u, v, w in mst1]  # 生成Kruskal算法需要的边集合
    nodes = set(graph.keys())  # 节点集合
    mst2 = kruskal(edges, nodes)
    if mst2 == "NO":
        print("NO")
        continue
    result = set([sum([x[2] for x in mst1])])  # 添加最小生成树的边权之和
    for edge1 in mst1:
        for edge2 in mst2:
            if set(edge1) & set(edge2):  # 如果两个最小生成树有交点,则跳过
                continue
            graph = {}
            for i in range(m):
                x, y, w = map(int, input().split())
                if x not in graph:
                    graph[x] = []
                if y not in graph:
                    graph[y] = []
                graph[x].append((y, w))
                graph[y].append((x, w))
            graph[edge1[0]].remove((edge1[1], edge1[2]))  # 删除edge1
            graph[edge1[1]].remove((edge1[0], edge1[2]))
            graph[edge2[0]].remove((edge2[1], edge2[2]))  # 删除edge2
            graph[edge2[1]].remove((edge2[0], edge2[2]))
            new_mst = prim(graph)  # 求新的最小生成树
            if new_mst != "NO":
                result.add(sum([x[2] for x in new_mst]))
    result = sorted(list(result))
    if result:
        print(" ".join([str(x) for x in result]))
    else:
        print("NO")

这段代码使用Prim算法求出一个最小生成树$T$,然后使用Kruskal算法求出另一个最小生成树$T'$。接下来,我们对于$T$中的每一条边$e$,删除它,并再次用Prim算法求出新的最小生成树$T''$。我们比较$T''$和$T'$的边权之和$sum(T'')$和$sum(T')$,如果它们相等,则$T''$也是一个最小生成树,将其边权之和加入结果集合中。最后,按升序输出结果集合中的元素。如果结果集合为空,则说明没有最小生成树,输出"NO"。