A: 樹學家丁丁妹
題目描述
為了響應國家“退耕還林”的號召,丁丁妹正在將她的大頭菜田改造成樹林。
然而這和這道題並沒有什么關系。
重要的是,丁丁妹思考了如下一個問題:
給定一個有n 個點m 條邊的無向圖,每條邊有一個邊權c 。
如何選擇n−1 條邊來讓這個無向圖連通,並且使得這n−1條邊的邊權之和最小呢?
顯然這個問題對於丁丁妹來說太困難了,於是她又花重金聘請了你,希望你來解決這個問題。
輸入描述
單組數據,第一行為兩個正整數n,m 。
接下來m 行,每行有三個正整數x,y,c ,表示x 號點和y號點之間存在一條邊權為c 的無向邊。
數據保證:
1. 對於80% 的數據, 1 ≤ n,m ≤ 1000
2. 對於100% 的數據,1≤n,m≤1000000
3. 對於100% 的數據, 1 ≤ c ≤ 100
輸出描述
一個整數 c ,代表邊權之和的最小值;若無法選擇n−1條邊讓圖連通,輸出− 1 。
樣例輸入
3 3
1 2 1
1 3 2
2 3 3
樣例輸出
3
思路:
這個題一看數據量 1e6 這么大,指定不能用二維數組,所以最短路或者dp直接求實在是行不通,
問的是聯通圖, 最短連通路徑,又需要壓縮空間來優化, 很容易就想到並查集
另外, 注意這里說的連通路徑不是那種"一筆畫的"歐拉路, 而是 ------- 連通的可交叉的路徑-------類似一棵樹
實際上---------最小生成樹------------就是我們要求的連通路徑
問題在於怎么使用並查集來表示一條完整的,"從1-n都能連通的路徑",另外每條路徑都該怎么算出來
並查集來表示存在的連通關系, 我們知道 find()函數就是為了 將所有連通的節點歸到同一個"根"上面,
這樣可以形成一個"同根樹",如果發現所有的節點都只有一個根, 說明這個圖是聯通的,
最小生成樹的求解-----Kruskal算法/Prim算法-----實際上就是貪心!!!
這里用Kruskal , 我們把所有的邊排序,每次操作,
- 檢查是否加入新邊,是否和已有邊連通沖撞
- 是則加入, 並且更新節點的根(合並)
最后只檢查新邊是否和成環,最后檢查是否所有的點都連通,檢查邊的數目是否為n-1即可
注意合並是怎樣的,比如
6 和 4
1 3 5 2 7
存在5,2 之間有一條邊, 顯然不是5-2合並,因為這樣就會有兩個根了, 檢測連通靠的是根是否相同,
所以, 另一棵樹的根, 也就是6 ,接到另一棵樹,
至於怎么接, 這里看需要, 如果保持結構的話, 就要以5為根旋轉成,
5 類似於平衡樹, 6的爸爸變成5, 然后 5 的爸爸變成2,這樣就保持了原來的結構性質
6
1 3
如果需要徹底壓縮,盡量優化查找時間,那就讓, 6, 1, 3, 5 的爸爸變成 4
假如不要求時間和效率, 我們只要求聯通路徑長, 簡單合並就可以了
----比如, 6---右邊的爸爸-------變成--------左邊的爸爸-------2也可以得到確結果,如下代碼, 但是會TE
1 int l=find(x[i].l); 2 int r=find(x[i].r); 3 f(r!=l) { 4 k++; 5 ans+=x[i].d; 6 f[r]=x[i].l; 7 }
改成了把6的爸爸變成4, 還是TE,6---右邊的爸爸-------變成--------左邊的爸爸-------2
1 if(r!=l) { 2 k++; 3 ans+=x[i].d; 4 f[r]=l; 5 }
原因在於沒有固定好一個策略合並, "右邊的合並到左邊的"並不是一個有序的策略,
因為節點是有序號的, 但是, 輸入的時候並沒有規定大的節點一定在右邊
也不能保證, 比如大的節點合並到小的那里去,
所以加個判斷條件,改成這樣就過了
1 if(r!=l) { 2 k++; 3 ans+=x[i].d; 4 if(r>l)f[r]=l; 5 else f[l]=r; 6 }
完整代碼:

1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 using namespace std; 6 typedef long long ll; 7 const int M=1000000+10; 8 int n,m; 9 struct D { 10 int l,r; 11 int d; 12 D() { 13 l=0,r=0,d=0; 14 } 15 } x[M]; 16 int f[M]; 17 void init() { 18 for(int i=1; i<=n; i++) { 19 f[i]=i; 20 } 21 } 22 int find(int t) { 23 return f[t]==t?t:find(f[t]); 24 } 25 /* 26 int find(int t1) { 27 int t=t1; 28 while(f[t]!=t) { 29 t=f[t]; 30 } 31 return t; 32 } 33 */ 34 bool cmp(D a,D b) { 35 return a.d<b.d; 36 } 37 int main () { 38 memset(x,0,sizeof(x)); 39 scanf("%d%d",&n,&m); 40 init(); 41 ll ans=0; 42 for(int i=1; i<=m; i++) { 43 int l,r; 44 scanf("%d%d%d",&x[i].l,&x[i].r,&x[i].d); 45 } 46 sort(x+1,x+1+m,cmp); 47 int k=0; 48 bool t=0; 49 for(int i=1; i<=m; i++) { 50 int l=find(x[i].l); 51 int r=find(x[i].r); 52 if(r!=l) { 53 k++; 54 ans+=x[i].d; 55 if(r>l)f[r]=l; 56 else f[l]=r; 57 } 58 if(k==n-1) { 59 t=1; 60 break; 61 } 62 } 63 if(t==0)cout<<"-1"; 64 else printf("%lld",ans); 65 cout<<"\n"; 66 return 0; 67 }