1. 程式人生 > >luogu P3366 【模板】最小生成樹(克魯斯卡爾演算法)

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;
}