CSP CCF 2018124 數據中心 並查集 kruskal算法求最小生成樹 C++類


樣例輸入

4
5
1
1 2 3
1 3 4
1 4 5
2 3 8
3 4 2ios

樣例輸出

4c++

樣例說明

下圖是樣例說明。算法

這道題看題幹很麻煩,可是想想,要求用時最少的樹結構,也就是求每層次中的最大值,再比較全部層次中的最大值,讓這個總最大值最小。那麼能夠想到,讓這種最大值最小,獲得的確定是這個圖的最小生成樹。那麼這個問題其實就是求這個圖的最小生成樹的最長邊。數組

對最小生成樹,咱們能夠採用prim算法和kruskal算法。數據結構

prim算法相似dijkstra算法,選擇鄰接矩陣實現,須要咱們用n*n維矩陣,對於題中條件,須要用25億個整型字符空間,顯然越界了。我用prim寫的程序,交上去只得了50分。爲了得滿分,仍是須要用kruskal算法。函數

kruskal算法比較容易理解,就是對全部邊升序排列,從最小邊開始,逐次向解中加入邊,每次加入前先判斷,此條邊加入後是否已成環,若是是,則跳過此邊換下一個,若是否,就在答案生成樹中加入此邊。這裏碰到一個問題,如何判斷是否成環?spa

這裏引入並查集數據結構,它可以在幾乎線性的時間複雜度內判斷元素是否屬於同一個集合。則應用到該題中,就是對於新邊的兩端點,若是咱們經過並查集查詢到它們已在同一個集合內,則加入該邊構成環,則不加入;若是沒在同一個集合內,則加入答案生成樹,而且將兩端點加入同一個集合內。code

並查集的實現是靜態樹結構,也就是數組模擬的樹。每個節點的編號同時做爲集合序號,該節點做爲屬於這一集合的全部元素的父節點。blog

並查集的創建通常只包括三個函數:init()、Find()、unite()、same().ci

init:初始化並查集
Find:尋找目標節點的父節點,也就是表明所在的集合
unite:將兩個節點合所在集合併到,合併規則依照所在集合樹的高度
same:判斷兩個節點是否在同一個集合內

下面在答案中進一步說明並查集和kruskal算法。

補充一點,爲了便於處理,我在程序中構造了一個edge類,表示邊。其實class和struct差很少。

  • class中默認是private,類外調用(通常寫算法競賽代碼經常使用類外調用)須要設置public
  • 記住其中構造函數的寫法,和運算符重載的寫法。
#include <bits/stdc++.h>
#define N 50005
#define M 100005
using namespace std;
int n,m,root,ans=-1;
class edge {
    public:
        int u,v,cost;
        edge() : u(0),v(0),cost(0) {}
        edge(int a,int b,int c) : u(a),v(b),cost(c){}
    bool operator < (edge const ed) const {
        return cost<ed.cost;
    }
};
edge es();
vector<edge> ve;

int par[N],h[N];    //建立並查集父節點集合par,和並查集樹高集合h。h用於並查集合並。
void init(int n);   //並查集初始化函數
int Find(int a);    //並查集查找函數
bool same(int a,int b);     //並查集判斷是否屬於同一個集合
void unite(int a,int b);    //並查集合並函數

int main()  {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int a,b,c,res=0;
    cin>>n>>m>>root;
    for (int i=0;i<m;i++)   {
        cin>>a>>b>>c;
        ve.push_back(edge(a,b,c));
    }
    init(n);
    sort(ve.begin(),ve.end());
    for (int i=0;i<(int)ve.size();i++)  {
        if (!same(ve[i].u,ve[i].v)) {               //若是該邊的兩定點構不成環,則該邊加入解中
            unite(ve[i].u,ve[i].v);
            int t=ve[i].cost;
            res+=t;
            ans=max(t,ans);
        }
    }
    cout<<ans<<endl;
    return 0;
}

void init(int n) {
    for (int i=0;i<=n;i++)   {
        par[i]=i;           //初始化,全部的節點的父節點設爲自身
        h[i]=0;             //全部並查集的高度設爲0,即樹高。
    }
}

int Find(int a)   {
    if (a==par[a])  return a;       //若是節點號等於父節點號,這說明找到了該集合的父節點,則返回
    return par[a]=Find(par[a]);     //路徑壓縮,這一步是並查集算法的精華!!!它可以將向根節點查找的路徑上的全部父節點所有掛靠到根節點上,從而大大縮短並查集接下來查找的執行時間。
}

void unite(int a,int b) {
    a=Find(a);
    b=Find(b);              //原先WA是由於我這裏沒有對a和b的賦值,直接寫了個判斷 if Find(a)==Find(b),找bug找了半天。此處必定注意,要用a、b的根節點來替換a、b。
    if (a==b)   return;
    if (h[a]<h[b])  par[a]=b;       //若是a集合樹高小於b集合樹高,則a掛靠到b上
    else {                              //若是b集合樹高小於a集合樹高
        par[b]=a;                       //首先將b掛靠到a上
        if (h[a]==h[b]) h[a]++;     //而後判斷ab樹高是否相同,該操做是否會令a樹變高。如等高,則掛靠會讓a樹高+1
    }
}

bool same(int a,int b)  {return Find(a)==Find(b);}      //並查集查找,返回對兩元素根節點的判等
相關文章
相關標籤/搜索