遇到了一道題,一開始以為是簡單的最小生成樹
做完發現一直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
圖畫出來大概是這樣子的,如果用prim算法的話就會和點的順序有關。可能是11,可能是12
所以我們需要一個適用於有向圖的算法
算法介紹
朱劉算法只有3步,然后不斷循環。
-
找到每個點的最小入邊。既然是生成樹,那么對於每個點來說,只要選一個權值最小的入邊就可以了。
貪心思想。因為如果不是最小入邊,那么它肯定不是最小樹形圖的一條邊,考慮它是沒有意義的。
-
找環。找環找的是最小入邊構成的新圖的環。如果沒找到環,那么一棵樹就已經形成了,
因為樹就是沒有環的圖。再因為邊權都是最小的,因此此時最小樹形圖就已經出來了,停止循環。
-
如果第2步中找到了環,那么這個環就可以縮成一個點。然后構造新圖,更新邊權。
示意圖大致如下:
實現
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 = "")