最小生成樹 & 洛谷P3366【模板】最小生成樹 & 洛谷P2820 局域網
阿新 • • 發佈:2019-04-27
圖片 最大 show bool sca 9.png www. pla ++
嗯...
理解生成樹的概念:
在一幅圖中將所有n個點連接起來的n-1條邊所形成的樹。
最小生成樹:
邊權之和最小的生成樹。
最小瓶頸生成樹:
對於帶權圖,最大權值最小的生成樹。
如何操作?
1.Prim算法(O(mlogn))
2.Kruskal算法(O(mlogn))
推薦使用第二種,無需建圖。
算法流程:
Prim算法:(思想類似dijkstra)
隨意選取一個點作為已訪問集合的第一個點,並將所有相連的邊加入堆中
從堆中找到最小的連接集合內和集合外點的邊,將邊加入最小生成樹中
將集合外點標記為已訪問,並將相連邊加入堆
重復以上過程直到所有點都在訪問集合中
Kruskal算法:(並查集思想)
將邊按照權值排序
依次枚舉每一條邊,若連接的兩點不連通則加入最小生成樹中
使用並查集維護連通性
模板代碼:
![技術分享圖片](/img/jia.gif)
1 int f[101], h; 2 struct node{ 3 int x, y, l; 4 } a[100001]; 5 inline bool cmp(node i, node j){ 6 return i.l < j.l; 7 } 8 inline int find(int x){ 9 if(x != f[x])//本身是否為父親節點 10 f[x] = find(f[x]);Kruskal11 return f[x]; 12 }//並查集操作 13 int main(){ 14 for(int i = 1; i <= n; i++){ 15 f[i] = i; 16 }//父節點初始化 17 sort(a+1, a+k+1, cmp);//排序 18 for(int i = 1; i <= k; i++){ 19 int r1 = find(a[i].x); 20 int r2 = find(a[i].y); 21 if(r1 != r2){ 22 f[r1] = r2;23 } 24 } 25 }
![技術分享圖片](/img/jia.gif)
#include<bits/stdc++.h> using namespace std; int n,m,a,b,c; int sum; int g[1001][1001],minn[1001]; bool u[1001]; int main(){ memset(g,0x7f,sizeof(g)); memset(minn,0x7f,sizeof(minn)); memset(u,true,sizeof(u)); cin>>n>>m; for(int i=1;i<=m;i++) { cin>>a>>b>>c; g[a][b]=g[b][a]=c; sum+=c; } minn[1]=0; for(int i=1;i<=n;i++){ int k=0; for(int j=1;j<=n;j++) if(u[j]&&minn[j]<minn[k]) k=j; u[k]=false; for(int j=1;j<=n;j++) if(u[j]&&g[k][j]<minn[j]) minn[j]=g[k][j]; } int total=0; for(int i=1;i<=n;i++) total+=minn[i]; cout<<sum-total<<endl; return 0; }Prim
模板題:
洛谷P3366【模板】最小生成樹:
題目鏈接:https://www.luogu.org/problemnew/show/P3366
思路:(Kruskal)
一道模板題,首先用一個結構體讀入,然後初始化父節點,再按邊權排序,然後用find函數分別找輸入時的兩個點的父節點,並判斷其中一個是否是另一個的父親,否則就進行合並,並將h+=a[i].l。(思路比較好理解)
![技術分享圖片](/img/jia.gif)
1 #include<cstdio> 2 #include<iostream> 3 #include<algorithm> 4 5 using namespace std; 6 7 int h, f[200005]; 8 9 struct node{ 10 int x, y, l; 11 } a[200005]; 12 13 inline bool cmp(node i, node j){ 14 return i.l < j.l; 15 } 16 17 inline int find(int x){ 18 if(x != f[x]) 19 f[x] = find(f[x]); 20 return f[x]; 21 } 22 23 int main(){ 24 int n, m; 25 scanf("%d%d", &n, &m); 26 for(int i = 1; i <= n; i++){ 27 f[i] = i; 28 } 29 for(int i = 1; i <= m; i++){ 30 scanf("%d%d%d", &a[i].x, &a[i].y, &a[i].l); 31 //h += a[i].l; 32 } 33 sort(a+1, a+m+1, cmp); 34 for(int i = 1; i <= m; i++){ 35 int r1 = find(a[i].x); 36 int r2 = find(a[i].y); 37 if(r1 != r2){ 38 f[r1] = r2; 39 h += a[i].l; 40 //h -= a[i].l; 41 } 42 } 43 printf("%d", h); 44 return 0; 45 }AC代碼
洛谷P2820 局域網:
題目鏈接:https://www.luogu.org/problemnew/show/P2820
思路:
首先這道題的問法就很模板:
很顯然“f(i,j)表示i,j之間連接的暢通程度”即為i到j點的權值;“除去一些連線,使得網絡中沒有回路,並且被除去網線的Σf(i,j)最大”很顯然是求最小生成樹。但註意一個細節,它與最小生成樹有所不同,它要求的是Σf(i,j)最大。
所以我們在最小生成樹的模板上進行修改即可:讀入時將所有的邊權都加到h中。在判斷父節點是否相同時,若不同,則將合並,並將合並的這條邊的權值減掉即可。
![技術分享圖片](/img/jia.gif)
1 #include<cstdio> 2 #include<iostream> 3 #include<algorithm> 4 5 using namespace std; 6 7 int f[101], h; 8 9 struct node{ 10 int x, y, l; 11 } a[100001]; 12 13 inline bool cmp(node i, node j){ 14 return i.l < j.l; 15 } 16 17 inline int find(int x){ 18 if(x != f[x]) 19 f[x] = find(f[x]); 20 return f[x]; 21 } 22 23 int main(){ 24 int n, k; 25 scanf("%d%d", &n, &k); 26 for(int i = 1; i <= n; i++){ 27 f[i] = i; 28 } 29 for(int i = 1; i <= k; i++){ 30 scanf("%d%d%d", &a[i].x, &a[i].y, &a[i].l); 31 h += a[i].l; 32 } 33 sort(a+1, a+k+1, cmp); 34 for(int i = 1; i <= k; i++){ 35 int r1 = find(a[i].x); 36 int r2 = find(a[i].y); 37 if(r1 != r2){ 38 f[r1] = r2; 39 h -= a[i].l; 40 } 41 } 42 printf("%d", h); 43 return 0; 44 }AC代碼
大概就是這樣,個人認為Kruskal算法比Prim算法寫起來簡單並好理解....
最小生成樹 & 洛谷P3366【模板】最小生成樹 & 洛谷P2820 局域網