python 使用 最大生成樹 解決 營救問題

源文雨發表於2020-11-28

題目描述

你是紅軍指揮官,在一場軍事演習中,你的部分軍隊被藍軍包圍了。藍軍包圍的方式如下
包圍
在上圖中,每個頂點表示藍軍的部隊,頂點中數字表示藍軍在此處的人數(千人),兩點間的邊表示藍軍兩個部隊間形成的火線,火線構成的圈即是一道包圍,一條火線的戰鬥力為其相連兩個部隊的人數和,也是你要進攻這條火線所要消耗的兵力。你可以同時進攻藍軍的多條火線,請以成本最低的方式打破藍軍包圍,營救被包圍的部隊,計算出所需要消耗的兵力數(千人)

輸入

  • 輸入包含多個測試例。第一行是一個整數n,表示測試測試例數量。
  • 對每個測試例:
    第一行是一個正整數m(2<m≤200),是藍軍部隊數。
    每個藍軍部隊有兩行:
    一行3個正整數:i為部隊編號(0≤i≤m-1),ai為藍軍部隊人數(千人)(1≤ai≤100),bi為與該部隊能形成火線的部隊數量(1≤bi≤m-1)。
    一行有bi個正整數,分別是與部隊i形成火線的部隊編號,數字間有空格。
  • 至少有一個包圍。

輸出

每個測試例輸出一行,是一個正整數:打破包圍營救戰友的最小消耗兵力。

輸入樣例

1 
3  
0 1 2 
1 2 
1 2 2 
0 2 
2 3 2 
0 1

sample

輸出結果

3

解題思路

solution

程式碼實現

使用並查集優化的Kruskal演算法實現

#!/usr/bin/env python3
#rescue.py
#fumiama 20201115

from heapq import heappop, heappush

class UnionFindSet(object):
    def __init__(self, nodes):
        self.fatherMap = {}
        self.setNumMap = {}
        for node in nodes:
            self.fatherMap[node] = node
            self.setNumMap[node] = 1
    def findFather(self, node):
        father = self.fatherMap[node]
        if (node != father):
            father = self.findFather(father)
        self.fatherMap[node] = father
        return father
    def isSameSet(self, a, b):
        return self.findFather(a) == self.findFather(b)
    def union(self, a, b):
        if a is None or b is None: return
        aFather = self.findFather(a)
        bFather = self.findFather(b)
        if (aFather != bFather):
            aNum = self.setNumMap[aFather]
            bNum = self.setNumMap[bFather]
            if (aNum <= bNum):
                self.fatherMap[aFather] = bFather
                self.setNumMap[bFather] = aNum + bNum
                self.setNumMap.pop(aFather)
            else:
                self.fatherMap[bFather] = aFather
                self.setNumMap[aFather] = aNum + bNum
                self.setNumMap.pop(bFather)

class Node(object):
    def __init__(self, name, size, links):
        self.name = name
        self.size = size
        self.links = links
    def __repr__(self):
        return "(節點:" + str(self.name) + ", 大小:" + str(self.size) + ", 連線到:" + str(self.links) + ")"

class Graph(object):
    def __init__(self):
        self.edges = []
        self.nodes = set()
        self.sizes = dict(map=int)
        self._edgesSets = []        #識別重複邊
    def addNode(self, node):
        self.nodes.add(node)
        self.sizes[node.name] = node.size
    def calcEdgeWeights(self):
        for node in self.nodes:
            for link in node.links:
                edge = (-(node.size + self.sizes[link]), node.name, link)   #邊權為負構造大頂堆
                sedge = {node.size + self.sizes[link], node.name, link}
                if sedge not in self._edgesSets:
                    heappush(self.edges, edge)
                    self._edgesSets.append(sedge)
    def rescue(self):
        forest = UnionFindSet(self.sizes.keys())
        edges = self.edges.copy()
        cost = 0
        while edges:
            edge = heappop(edges)
            if forest.isSameSet(edge[1], edge[2]): cost -= edge[0]
            else: forest.union(edge[1], edge[2])
        return cost

    def __repr__(self):
        return "圖資訊:\n邊: " + str(self.edges) + "\n點: " + str(self.nodes)


if __name__ == '__main__':
    n = int(input())
    graph = Graph()
    while n:
        m = int(input())
        while m:
            no, size, edgeCnt = map(int, input().split())
            links = [int(x) for x in input().split()]
            graph.addNode(Node(no, size, links))
            m -= 1
        graph.calcEdgeWeights()
        #print(graph)
        minCost = graph.rescue()
        #print("最小兵力:", minCost)
        print(minCost)
        n -= 1

相關文章