luogu P3366 【模板】最小生成樹(克魯斯卡爾演算法)
題目描述
如題,給出一個無向圖,求出最小生成樹,如果該圖不連通,則輸出orz
輸入輸出格式
輸入格式:
第一行包含兩個整數N、M,表示該圖共有N個結點和M條無向邊。(N<=5000,M<=200000)
接下來M行每行包含三個整數Xi、Yi、Zi,表示有一條長度為Zi的無向邊連線結點Xi、Yi
輸出格式:
輸出包含一個數,即最小生成樹的各邊的長度之和;如果該圖不連通則輸出orz
輸入輸出樣例
輸入樣例#1: 複製
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3
輸出樣例#1 複製:
7
說明
時空限制:1000ms,128M
資料規模:
對於20%的資料:N<=5,M<=20
對於40%的資料:N<=50,M<=2500
對於70%的資料:N<=500,M<=10000
對於100%的資料:N<=5000,M<=200000
自己的理解:
克魯斯卡爾基本上就是並查集加貪心(個人理解)。結構體存邊,這樣便於排序,畢竟每條邊的全職和連線點需要一一對應;之後把所有的邊排序,鑑於本人較懶,直接sort了;然後就是列舉從小到大開始列舉所有邊,因為是最小生成樹,所加的邊肯定是從小的邊開始;這就是我們的一個貪心思想;需要在列舉時判斷連個點是否在最小生成樹中,如果不在就sum就加上該邊權值(sum表示最小生成樹的權值和),然後有用並查集把他們放進一個集合;如果在最小生成樹中,即已經標記過,直接跳過。連線n個點需要的邊必定是n-1條(不信的話可以自己畫一下);所以當所有的進入最小生成樹中的邊數為n-1時可以直接跳出,最小生成樹已經找出;
要點步驟:
1.結構體存邊;
2.對所有的邊進行排序;
3.從小到大列舉所有邊;
4.將可以的邊(即還未在最小生成樹中的點)通過並查集合並,同時計算權值和;
5.如果已經有n-1條邊則最小生成樹已經找出;
(我犯了一個錯誤就是沒有考慮orz的情況,不過luogu的題解也有很多沒有考慮這種情況,可見資料還可以加強,,,)
經學長點撥,只需要在外面判斷一下,所加邊數不等於n-1即最小生成樹未生成,也就說明,有未聯通的點。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cstdlib> using namespace std; inline int read(){//讀入優化 int x = 0;int f = 1;char c = getchar(); while(c<'0' || c>'9'){if(c=='-')f=-f;c=getchar();} while(c>='0' && c<='9'){x=x*10+c-'0';c=getchar();} return x*f; } struct edge{//結構體存邊 int u;int v;int w; }; struct edge e[200005]; int f[500521]; int sum; int ans; int n;int m; bool cmp(edge x,edge y){ return x.w<y.w; } int query(int v){//並查集查詢 if(f[v] == v)return v; else return f[v] = query(f[v]);//路徑壓縮 } int merge(int v,int u){//並查集合並 int t1 = query(v); int t2 = query(u); if(t1 != t2){ if(rand()%2)f[t1] = t2;//zhx教的防止並查集被卡的小妙招 else f[t2] = t1; return 1; } return 0; } int main(){ n = read();m = read(); for(int i = 1;i <= m;i++){ e[i].u = read(); e[i].v = read(); e[i].w = read(); } sort(e+1,e+m+1,cmp);//從小到大排序 以e[I].w為關鍵字進行排序 for(int i = 1;i <= n; i++)f[i] = i; //kruskal for(int i = 1;i <= m; i++){//列舉所有邊 if(merge(e[i].u,e[i].v)){//如果雙方有一個或者都未進最小生成樹 ans++;sum += e[i].w; //ans表示已經找出最小生成樹中多少邊 // 並求出權值和 } if(ans == n-1)break;//找出一個最小生成樹 } printf("%d",sum); return 0; }
考慮圖未聯通的情況的程式碼:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
using namespace std;
inline int read(){
int x = 0;int f = 1;char c = getchar();
while(c<'0' || c>'9'){if(c=='-')f=-f;c=getchar();}
while(c>='0' && c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
struct edge{
int u;int v;int w;
};
struct edge e[200005];
int f[500521];
int sum;
int ans;
int n;int m;
bool cmp(edge x,edge y){
return x.w<y.w;
}
int query(int v){
if(f[v] == v)return v;
else return f[v] = query(f[v]);
}
int merge(int v,int u){
int t1 = query(v);
int t2 = query(u);
if(t1 != t2){
if(rand()%2)f[t1] = t2;
else f[t2] = t1;
return 1;
}
return 0;
}
int main(){
n = read();m = read();
for(int i = 1;i <= m;i++){
e[i].u = read();
e[i].v = read();
e[i].w = read();
}
sort(e+1,e+m+1,cmp);
for(int i = 1;i <= n; i++)f[i] = i;
//kruskal
for(int i = 1;i <= m; i++){
if(merge(e[i].u,e[i].v)){
ans++;sum += e[i].w;
}
if(ans == n-1)break;
}
if(ans != n-1){//判斷一下是否生成最小生成樹如果生成直接輸出
printf("orz");
return 0;//程式結束
}
printf("%d",sum);
return 0;
}