本題模板,最小生成樹,洛谷P3366
題目描述
如題,給出一個無向圖,求出最小生成樹,如果該圖不連通,則輸出orz
輸入輸出格式
輸入格式:
第一行包含兩個整數N、M,表示該圖共有N個結點和M條無向邊。(N<=5000,M<=200000)
接下來M行每行包含三個整數Xi、Yi、Zi,表示有一條長度為Zi的無向邊連接結點Xi、Yi
輸出格式:
輸出包含一個數,即最小生成樹的各邊的長度之和;如果該圖不連通則輸出orz
輸入輸出樣例
說明
時空限制:1000ms,128M
數據規模:
對於20%的數據:N<=5,M<=20
對於40%的數據:N<=50,M<=2500
對於70%的數據:N<=500,M<=10000
對於100%的數據:N<=5000,M<=200000
下面介紹堆優化后的Prim
首先清楚prim的過程:
給出一個適用於這類問題的推論:給定一張無向圖G=(V,E)。n=V的大小,m=E的大小。從E中選出k<n-1條邊構成G的一個生成森林。若再從剩余的m-k條邊選n-1-k條添加到生成森林中,使其成為G的生成樹,並且選出的邊的權值之和最小,則該生成樹一定包含這m-k條邊中連接兩個森林的不連通節點的最小邊
無論是Prim還是Kruskal都是基於這個推論,但Prim略微有一些改動。
Prim算法總是維護最小生成樹的一部分。最初,Prim算法僅確定1號節點屬於最小生成樹。在任意的時刻,設已經確定屬於最小生成樹的節點集合為T,剩余節點集合為S。Prim算法找到min(x屬於S,y屬於T){z},即兩個端點分別屬於集合S,T的權值最小的邊,然后把點x從集合S中刪除,加入到集合T中去,並把Z累計到答案(最后答案就是最小生成樹的邊權值和)。
具體來說,可以維護數組d:對於x屬於S,則把d[x]表示節點x與集合T中的節點之間權值最小的邊的權值。若x屬於T,則d[x]就等於x被加入T選出的最小邊的權值
用一個數組標記節點是否屬於T。每次從未標記的節點中選出d值最小的,把它標記(加入T),同時
掃描所有出邊,更新另一個端點的d值。最后得出答案
可用二叉堆將上述的d數組優化,但其實都不如Kruskal方便。因此Prim主要用於稠密圖,尤其是完全圖的最下生成樹的求解
那么所謂的二叉堆優化實際上就是對於每一次拓展的邊加入到一個小根堆中,下面筆者的代碼實現用的是priority_queue(我懶)
具體實現看代碼
注意不僅要判斷堆是否為空還要統計已經維護的點的個數,確保還是小於等於n的
#include<bits/stdc++.h>
#define ll long long
using namespace std; const int maxn=2e5+15; const int mxn=5e3+15; struct node { int t;int d; bool operator < (const node &a) const { return d>a.d; } }; int n,m; int vis[mxn]; vector <node> e[mxn]; priority_queue <node> q; inline int read() { char ch=getchar(); int s=0,f=1; while (!(ch>='0'&&ch<='9')) {if (ch=='-') f=-1;ch=getchar();} while (ch>='0'&&ch<='9') {s=(s<<3)+(s<<1)+ch-'0';ch=getchar();} return s*f; } ll prim() { ll ans=0; int cnt=0; q.push((node){1,0}); while (!q.empty()&&cnt<=n) { node k=q.top();q.pop(); if (vis[k.t]) continue; vis[k.t]=1; ans+=k.d; cnt++; for (int i=0;i<e[k.t].size();i++) if (!vis[e[k.t][i].t]){ q.push((node){e[k.t][i].t,e[k.t][i].d}); } } return ans; } int main() { n=read();m=read(); for (int i=1;i<=m;i++) { int x=read(),y=read(),z=read(); e[x].push_back((node){y,z});e[y].push_back((node){x,z}); } printf("%lld",prim()); return 0; }
