從題目不難看出這是一道最小生成樹的裸題,然後再看資料範圍:
$n <= 2300 $
\(m <= 400000\)
首先想到kruskal或者prim都可以滿足需要,但考慮到這兩種演算法中kruskal應用更廣,所以今天我們重點講一下kruskal演算法。
首先回歸最小生成樹的定義:給定一張邊帶權的無向圖\(g = (v, e)\),\(n=|v|\),由\(v\)中\(n\)個節點和\(e\)中\(n-1\)條邊構成的無向連通圖稱為\(g\)的一棵生成樹。邊的權值之和最小的生成樹被稱為最小生成樹。
進入今天的正題:kruskal演算法
kruskal演算法是基於一種貪心的思想,他總是維護無向圖的最小生成森林。最初,生成森林是由零條邊構成,每個節點構成一棵僅包含乙個節點的樹。
kruskal演算法的主體步驟,就是不斷從剩餘的邊中選出一條權值最小,且他的兩個端點在生成森林中分屬於兩棵不同的樹,將這條邊加入生成森林中。
那麼,我們自然就會想到圖的連通性應該如何維護呢? 為了**效率和簡便,我們一般都採用並查集來維護。(事實上,kruskal演算法的巨大優越性也是在加入並查集優化後才得以體現的,而我們平時所說的kruskal一般都是帶並查集優化的。)
演算法流程:
1.建立並查集,每個點構成乙個集合。
2.將各邊按權值從小到大排序,依次掃瞄每條邊。
3.若一條邊兩端點在同一集合中,則忽略這條邊直接跳過。
4.否則,合併他們所在的集合,將權值之和累加到答案中。
5.第四步中選中的邊就構成了這張圖的最小生成樹。
一圖勝千言:
這是一幅無向圖:
將各邊按權值從小到大排完序後應為:
x y val
1 2 2
1 3 2
1 4 3
3 4 3
2 3 4
然後我們開始執行kruskal的主體流程:
列舉第一條邊(1, 2, 2),將其加入生成森林中,生成森林變為:
列舉第二條邊(1, 3, 2),將其加入生成森林中,生成森林變為:
列舉第三條邊(3,4,3),將其加入生成森林中,生成森林變為:
你會發現,此時我們已經選出\(n - 1\)條邊,那麼這就是這張圖的最小生成樹,將權值累加起來即為答案,kruskal演算法結束。
ps:這幅圖比較簡單,但是重在理解kruskal的流程。
原理理解之後,**就會變得異常簡單了,注意,重點在於一定要理解,廢話不多說,直接上**:
#include #include #include #include #define maxn 400005
using namespace std;
int n, m, fa[maxn], tot, head[maxn], sum;
struct edgeedge[maxn];
bool cmp(edge x,edge y) //自定義比較函式
int find(int x)
bool judge(int x,int y)
void merge(int x,int y)
//優雅的三行並查集
int main()
sort(edge+1, edge+m+1, cmp); //權值從大到小排序
for(int i = 1; i <= n; i++) fa[i] = i;
for(int i = 1; i <= m; i++)
} //當然,這部分也可以封裝到函式中,看個人喜好
cout << sum << '\n';
return 0;
}
最後,本篇題解旨在幫助那些剛剛接觸到最小生成樹的oier們更好的理解,若有任何問題,請私信本人。 洛谷 P2126 Mzc家中的男家丁
題目背景 與 的 還沒有眾人皆知,所以我們要來宣傳一下。家很有錢 開玩笑 他家有 個男家丁,現在 要將她們全都聚集起來 幹什麼就不知道了 現在知道 與男家丁們互相之間通訊的時間,請算出把他們每個人叫到需要的總時間 要重複的哦 保證能把他們每個人叫到。輸入格式 第一行有乙個數n,表示有n個男家丁。第二...
洛谷 P2126 Mzc家中的男家丁
與 的 還沒有眾人皆知,所以我們要來宣傳一下。家很有錢 開玩笑 他家有 個男家丁,現在 要將她們全都聚集起來 幹什麼就不知道了 現在知道 與男家丁們互相之間通訊的時間,請算出把他們每個人叫到需要的總時間 要重複的哦 保證能把他們每個人叫到。輸入格式 第一行有乙個數n,表示有n個男家丁。第二行乙個數m...
洛谷 2126 Mzc家中的男家丁
mzc與djn的 還沒有眾人皆知,所以我們要來宣傳一下。mzc家很有錢 開玩笑 他家有 個男家丁,現在mzc要將她們全都聚集起來 幹什麼就不知道了 現在知道mzc與男家丁們互相之間通訊的時間,請算出把他們每個人叫到需要的總時間 要重複的哦 保證能把他們每個人叫到。第一行有乙個數n,表示有n個男家丁。...