P4716 朱劉算法/最小樹形圖/有向圖最小生成樹 python實現


遇到了一道題,一開始以為是簡單的最小生成樹
做完發現一直WA,學習了一下發現是朱劉算法,整理一下筆記

P4716 最小樹形圖

地址:https://www.luogu.com.cn/problem/P4716

題目背景

這是一道模板題。

題目描述

給定包含 nnn 個結點, mmm 條有向邊的一個圖。試求一棵以結點 rrr 為根的最小樹形圖,並輸出最小樹形圖每條邊的權值之和,如果沒有以 rrr 為根的最小樹形圖,輸出 −1-1−1。

輸入格式

第一行包含三個整數 n,m,rn,m,rn,m,r,意義同題目所述。

接下來 mmm 行,每行包含三個整數 u,v,wu,v,wu,v,w,表示圖中存在一條從 uuu 指向 vvv 的權值為 www 的有向邊。

輸出格式

如果原圖中存在以 rrr 為根的最小樹形圖,就輸出最小樹形圖每條邊的權值之和,否則輸出 −1-1−1。

輸入輸出樣例

輸入 #1

4 6 1
1 2 3
1 3 1
4 1 2
4 2 2
3 2 1
3 4 1

輸出 #1

3

輸入 #2

4 6 3
1 2 3
1 3 1
4 1 2
4 2 2
3 2 1
3 4 1

輸出 #2

4

輸入 #3

4 6 2
1 2 3
1 3 1
4 1 2
4 2 2
3 2 1
3 4 1

輸出 #3

-1

說明/提示

樣例 111 解釋

最小樹形圖中包含第 222, 555, 666 三條邊,總權值為 1+1+1=31 + 1 + 1 = 31+1+1=3

樣例 222 解釋

最小樹形圖中包含第 333, 555, 666 三條邊,總權值為 2+1+1=42 + 1 + 1 = 42+1+1=4

樣例 333 解釋

無法構成最小樹形圖,故輸出 −1-1−1 。

數據范圍

對於所有數據,1≤u,v≤n≤1001 \leq u, v \leq n \leq 1001≤u,v≤n≤100, 1≤m≤1041 \leq m \leq 10^41≤m≤104, 1≤w≤1061 \leq w \leq 10^61≤w≤106。

最小樹形圖

一個有向圖,存在從某個點為根的,可以到達所有點的一個最小生成樹,則它就是最小樹形圖。

簡單來說,就是有向圖的最小生成樹

朱劉算法

為什么需要?

如果是無向圖,用prim或者kruskal算法很簡單

數據結構與算法筆記:最小生成樹Kruskal、Prim算法與JAVA實現

但是如果是有向圖,就會有一些問題

舉個例子

第一行包含三個整數n,m,r,意義同題目所述。
接下來m行,每行包含三個整數u, v, w,表示圖中存在一從u指向V的權值為w的有向邊。
對於所有數據,1≤u,v≤n≤100,1≤m≤10^4,1≤w≤10^6。

輸入:
3 4 1
1 2 8 
1 3 8
2 3 4 
3 2 3

img

圖畫出來大概是這樣子的,如果用prim算法的話就會和點的順序有關。可能是11,可能是12

所以我們需要一個適用於有向圖的算法

算法介紹

朱劉算法只有3步,然后不斷循環。

  1. 找到每個點的最小入邊。既然是生成樹,那么對於每個點來說,只要選一個權值最小的入邊就可以了。

    貪心思想。因為如果不是最小入邊,那么它肯定不是最小樹形圖的一條邊,考慮它是沒有意義的。

  2. 找環。找環找的是最小入邊構成的新圖的環。如果沒找到環,那么一棵樹就已經形成了,

    因為樹就是沒有環的圖。再因為邊權都是最小的,因此此時最小樹形圖就已經出來了,停止循環。

  3. 如果第2步中找到了環,那么這個環就可以縮成一個點。然后構造新圖,更新邊權。

示意圖大致如下:

1584627698430

實現

class Edge:
    def __init__(self, u, v, w):
        self.u = u
        self.v = v
        self.w = w

    def __str__(self):
        return str(self.u) + str(self.v) + str(self.w)


def zhuliu(edges, n, m, root):
    res = 0
    while True:
        pre = [-1]*n
        visited = [-1] * n
        circle = []
        inderee = [INF] * n
        # 尋找最小入邊
        inderee[root] = 0
        for i in range(m):
            if edges[i].u != edges[i].v and edges[i].w < inderee[edges[i].v]:
                pre[edges[i].v] = edges[i].u
                inderee[edges[i].v] = edges[i].w
        # 有孤立點,不存在最小樹形圖
        for i in range(n):
            if i != root and inderee[i] == INF:
                return -1
        # 找有向h環
        tn = 0  # 記錄環的個數
        circle = [-1] * n
        for i in range(n):
            res += inderee[i]
            v = i
            # 向前遍歷找環,中止情況有:
            # 1. 出現帶有相同標記的點,成環
            # 2. 節點屬於其他環,說明進了其他環
            # 3. 遍歷到root了
            while visited[v] != i and circle[v] == -1 and v != root:
                visited[v] = i
                v = pre[v]
            # 如果成環了才會進下面的循環,把環內的點的circle進行標記
            if v != root and circle[v] == -1:
                while circle[v] != tn:
                    circle[v] = tn
                    v = pre[v]
                tn += 1
        # 如果沒有環了,說明一定已經找到了
        if tn == 0:
            break
        # 否則把孤立點都看作自環看待
        for i in range(n):
            if circle[i] == -1:
                circle[i] = tn
                tn += 1
        # 進行縮點,把點號用環號替代
        for i in range(m):
            v = edges[i].v
            edges[i].u = circle[edges[i].u]
            edges[i].v = circle[edges[i].v]
            # 如果邊不屬於同一個環
            if edges[i].u != edges[i].v:
                edges[i].w -= inderee[v]
        n = tn
        root = circle[root]
    return res


INF = 9999999999
if __name__ == '__main__':
    n, m, root = list(map(int, input().split()))
    edges = []
    for i in range(m):
        u, v, w = list(map(int, input().split()))
        # 輸入的點是1開始的,-1改為0開始的
        edges.append(Edge(u-1, v-1, w))
    print(zhuliu(edges, n, m, root-1),end = "")


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM