最小生成樹

最小生成樹基礎

定義

對於圖 $ G = (V,E) $, 有 \(n\) 個點, \(m\) 條邊, 由 \(V\) 中全部 \(n\) 個點和 \(E\) 中 \(n-1\) 條邊構成的一個連通子圖(即一棵樹),稱爲 \(G\) 的一個生成樹, 邊權值最小的爲最小生成樹.ios

求解方法:

  1. prim算法  \(O(n^2)\)
  2. kruskal算法  \(O(mlogn)\)

prim算法

通常用於稠密圖:算法

#include <iostream>
#include <cstring>

using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 510;
int n,m;
int g[N][N];    //稠密圖
int dist[N];    //表示某個結點到當前集合的最小距離(與dijkstra不一樣)
int st[N];      //是否在集合內

int prim()
{
    memset(dist, INF, sizeof dist);
    
    int res = 0;
    for (int i = 0; i < n; i ++ ){
        
        int t = -1; 
        for (int j = 1; j <= n; j ++ )          //尋找不在集合內,且到集合距離最小的結點
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;
            
        st[t] = 1;      //進入集合
        
        if (i && dist[t] == INF)    return INF; //不存在生成樹
        if (i)  res += dist[t];
            
        for (int j = 1; j <= n; j ++ )      //更新其它結點
            dist[j] = min(dist[j], g[t][j]);
    }
    return res;
}

int main()
{
    cin >> n >> m;
    
    memset(g, INF, sizeof g);
    
    for (int i = 1; i <= m; i ++ ){
        int a, b, v;
        cin >> a >> b >> v;
        g[a][b] = g[b][a] = min(g[a][b], v);    //無向圖
    }
    
    int t = prim();
    
    if (t == INF)
        puts("impossible");
    else    
        cout << t << endl;
        
    return 0;
}

kruskal算法

通常用於稀疏圖網絡

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 1e5 + 10;
const int M = 2 * N;
int n,m;

int f[N];       //並查集的操做

struct Edge{
    int a, b;
    int v;
}edge[M];

bool comp(Edge x, Edge y)       //自定義比較
{
    return x.v < y.v;
}

int find(int x)                 //尋找祖宗結點
{
    if (f[x] != x)  return f[x] = find(f[x]);
    return f[x];
}

int main()
{
    cin >> n >> m;
    
    for (int i = 1; i <= m; i ++ ){
        int a, b, v;
        cin >> a >> b >> v;
        edge[i] = {a, b, v};
    }
    
    sort(edge + 1, edge + m + 1, comp);
    
    for (int i = 1; i <= n; i ++ )      //並查集的初始化
        f[i] = i;
    
    int res = 0;        //記錄距離之和
    int cnt = 0;        //存儲結點數量
    for (int i = 1; i <= m; i ++ ){      //枚舉每條邊
        
        int a = edge[i].a;
        int b = edge[i].b;
        int v = edge[i].v;
        
        int fa = find(a);
        int fb = find(b);
        
        if (fa != fb){           //合併集合
            f[fa] = fb;
            res += v;
            cnt ++;
        }
    }
    if (cnt < n - 1)
        puts("impossible");
    else
        cout << res << endl;
    
    return 0;
    
}

簡單應用

AcWing 1140. 最短網絡

算法思路:

最小生成樹的模板題, 輸入矩陣形式, 採用prim算法ide

#include <iostream>
#include <cstring>

using namespace std;

const int N = 110;

int g[N][N];
int n;
int dist[N];
int st[N];

int prim()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    int res = 0;
    
    for (int i = 1; i <= n; i ++ ){
        int t = -1;
        
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;
        
        st[t] = 1;
        
        res += dist[t];
        
        for (int j = 1; j <= n; j ++ )
            dist[j] = min(dist[j], g[t][j]);
    }
    
    return res;
}

int main()
{
    cin >> n;
    
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            cin >> g[i][j];
        
    cout << prim() << endl;
    
    return 0;
}

AcWing 1141. 局域網

算法思路 :

kruakal算法求最小生成樹, 注意每次當兩個點須要刪邊時, 將結果加上權值.spa

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110, M = 220;

struct Edge{
    int a, b, v;
    
    bool operator < (const Edge &t)const 
    {
        return v < t.v;
    }
}edge[M];
int fa[N];
int n ,m;

int find(int x)
{
    if (fa[x] != x)
        fa[x] = find(fa[x]);
    else
        return x;
}


int main()
{
    cin >> n >> m;
    int res = 0;
    for (int i = 1; i <= n; i ++ )
        fa[i] = i;
    
    for (int i = 1; i <= m; i ++ ){
        int a, b, v;
        cin >> a >> b >> v;
        edge[i] = {a, b, v};
    }
    
    sort(edge + 1, edge + m + 1);
    
    for (int i = 1; i <= m; i ++ )
        cout << edge[i].v << endl;
    for (int i = 1; i <= m; i ++ ){
        int a = edge[i].a;
        int b = edge[i].b;
        int v = edge[i].v;
        
        int aa = find(a);
        int bb = find(b);
        
        if (aa != bb)   fa[aa] = bb;
        else
            res += v;
    }
    
    cout << res << endl;
    
    return 0;
}

AcWing 1142. 繁忙的都市

算法思路 :

一樣是模板題, 注意理解題意, 要求被改造的道路可以連通整個圖(被選入生成樹裏的點)排序

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 310, M = 8010;

struct Edge{
    int a, b, v;
    bool operator < (const Edge &t)const
    {
        return v < t.v;
    }
}edge[M];

int n,m;
int fa[N];
int sum;

int find(int x)
{
    if (fa[x] != x)
        fa[x] = find(fa[x]);
    else
        return x;
}


int main()
{
    cin >> n >> m;
    int res = 0;
    for (int i = 1; i <= m; i ++ ){
        int a, b, v;
        cin >> a >> b >> v;
        edge[i] = {a, b, v};
    }
    
    sort(edge + 1, edge + m + 1);
    
    for (int i = 1; i <= n; i ++ )
        fa[i] = i;
        
    for (int i = 1; i <= m; i ++ ){
        int a = edge[i].a;
        int b = edge[i].b;
        int v = edge[i].v;
        
        int aa = find(a);
        int bb = find(b);
        //cout << aa << ' ' << bb << endl;
        if (aa == bb)
            continue;
        else{
            sum ++;
            fa[aa] = bb;
            res = v;
            
        }
    }
    
    cout << sum << ' ' << res << endl;
    
    return 0;
    
}

AcWing 1143. 聯絡員

算法思路 :

圖中含有必選邊和非必選邊, 對於必選邊咱們直接加入到生成樹中, 而後再根據kruskal算法加入非必選邊ci

  • kruskal算法求最小生成樹能夠從中間開始
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 2010, M = 10010;

struct Edge{
    int op, a, b, v;
    bool operator < (const Edge &t)const
    {
        if (op == t.op)
            return v < t.v;
        return op < t.op;
    }
}edge[M];
int fa[N];
int n, m;


int find(int x)
{
    if (fa[x] != x)
        fa[x] = find(fa[x]);
    else
        return x;
}

int main()
{
    cin >> n >> m;
    int res = 0;
    for (int i = 1; i <= n; i ++ )
        fa[i] = i;
        
    for (int i = 1; i <= m; i ++ ){
        int op, a, b, v;
        cin >> op >> a >> b >> v;
        edge[i] = {op, a, b, v};
    }
    
    sort(edge + 1, edge + m + 1);
    
    for (int i = 1; i <= m; i ++ ){
        int op = edge[i].op;
        int aa = find(edge[i].a);
        int bb = find(edge[i].b);
        int v = edge[i].v;
        
        if (op == 1){
            res += v;
            if (aa != bb)
                fa[aa] = bb;
        }else{
            if (aa == bb)
                continue;
            else{
                res += v;
                fa[aa] = bb;
            }
        }
    }

        
    cout << res << endl;
    
    return 0;
}

AcWing 1144. 鏈接格點

算法思路 :

原圖中有 \(m * n\) 個點, 其中橫向點之間、縱向點之間都有邊, 根據kruskal算法,先加入縱向邊(權值小),再加入橫向邊(權值大).
咱們能夠直接在加邊時進行排序,避免sort().get

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 2 * 1e6 + 10, M = 2 * 1e6 + 10;
int g[1010][1010];
struct Edge{
    int a, b, v;
    
    bool operator < (const Edge &t)const
    {
        return v < t.v;
    }
}edge[M];
int fa[N];
int n, m;

int find(int x)
{
    if (fa[x] != x)
        fa[x] = find(fa[x]);
    return fa[x];
}

int main()
{
    cin >> n >> m;
    for (int i = 1, t = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            g[i][j] = t ++;
    
    for (int i = 1; i <= n * m; i ++ )
        fa[i] = i;
    
    int cnt = 0;
    for (int i = 1; i <= m; i ++ )
        for (int j = 1; j < n; j ++ ){
            int a = g[j][i], b = g[j + 1][i];
            int v = 1;
            //cout << a << ' ' << b << endl;
            edge[cnt ++] = {a, b, v};
        }
    
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j < m; j ++ ){
            int a = g[i][j], b = g[i][j + 1];
            int v = 2;
            //cout << a << ' ' << b << endl;
            edge[cnt ++] = {a,b,v};
        }
    int x1, y1, x2, y2;
    while (~scanf("%d%d%d%d",&x1, &y1, &x2, &y2)){
        int a = g[x1][y1];
        int b = g[x2][y2];
        int aa = find(a);
        int bb = find(b);
        fa[aa] = bb;
    }

    int res = 0;
    
    for (int i = 0; i < cnt; i ++ ){
        int aa = find(edge[i].a);
        int bb = find(edge[i].b);
        int v = edge[i].v;
        if (aa == bb)
            continue;
        else{
            res += v;
            fa[aa] = bb;
        }
    }
    
    cout << res << endl;
    
    return 0;
    
}

拓展應用

關於最小生成樹的基本定理:

  1. 任意一棵最小生成樹必定包含無向圖中權值最小的邊.
  2. 給定一張無向圖 \(G = (V, E)\) , 從 \(E\) 中選出 \(k < n - 1\) 條邊構成的 \(G\) 的一個生成森林, 若再從剩餘的 \(m - k\) 條邊中選 \(n - 1 - k\) 條添加到生成森林中, 使其成爲 \(G\) 的生成樹,而且選出的邊的權值之和最小,則該生成樹必定包含這 \(m - k\) 條邊中鏈接生成森林的兩個不連通的節點的權值最小的邊.

AcWing 1146. 新的開始

算法思路 :

圖論中經常使用的假設虛擬原點問題, 假設一個虛擬原點, 到點 \(i\) 的邊的權值爲在該點創建電站\(v_{i}\), 求一個包含全部點的最小生成樹.string

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 310;

int g[N][N];
int v[N];
int n;
int dist[N];
int st[N];

int prim()
{
    memset(dist, 0x3f, sizeof dist);
    dist[n + 1] = 0;
    int res = 0;
    for (int i = 0; i <= n; i ++ ){
        int t = -1;
        
        for (int j = 1; j <= n + 1; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;
        
        st[t] = 1;
        
        if (i)
            res += dist[t];
        
        for (int j = 1; j <= n + 1; j ++ )
            dist[j] = min(dist[j], g[t][j]);
    }

    return res;
}


int main()
{
    cin >> n;
    
    for (int i = 1; i <= n; i ++ )
        cin >> v[i];
    
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            cin >> g[i][j];
    for (int i = 1; i < n + 1; i ++ )
        g[n + 1][i] = g[i][n + 1] = v[i];
    
    g[n + 1][n + 1] = 0;
    
    cout << prim() << endl;
    
    return 0;
}

AcWing 1145. 北極通信網絡

算法思路:

圖中有 \(n\) 個點, 有 \(n*n\) 條路徑. 假設起始狀態都不連通, 即有 \(n\) 個連通塊, 根據kruakal算法,每次找到一個邊權值最小的點, 把該點加入到生成樹中, 直到剩餘 \(k\) 個連通塊, 咱們能夠直接用衛星連通.it

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;
typedef pair<int,int> PII;
const int N = 510, M = N * N;
int n, k;

struct Edge{
    int a, b;
    double v;
    
    bool operator < (const Edge &t)const
    {
        return v < t.v;
    }
}edge[M];
PII p[N];
int fa[N];

int find(int x)
{
    if (fa[x] != x)
        fa[x] = find(fa[x]);
    return fa[x];
}

double get_len(PII a, PII b)
{
    double dx = a.first - b.first;
    double dy = a.second - b.second;
    return sqrt(dx * dx + dy * dy);
}

int main()
{
    cin >> n >> k;
    for (int i = 1; i <= n; i ++ ){
        cin >> p[i].first >> p[i].second;
        fa[i] = i;
    }
    
    int cnt = 0;
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= i; j ++ ){
            double v = get_len(p[i], p[j]);
            edge[cnt ++] = {i,j,v};
        }
    
    sort(edge, edge + cnt);
    
    
    double ans = 0;
    int block = n;
    for (int i = 0; i < cnt; i ++ ){
        int aa = find(edge[i].a);
        int bb = find(edge[i].b);
        double v = edge[i].v;
        if (block == k)
            break;
        ans = v;
        
        if (aa != bb){
            block --;
            fa[aa] = bb;
        }
        
        
    }
    
    printf("%.2f", ans);
    
    return 0;
}

AcWing 346. 走廊潑水節

算法思路:

徹底圖 : 徹底圖是一個簡單的無向圖,其中每對不一樣的頂點之間都恰連有一條邊相連.

題目要求: 知足圖的惟一最小生成樹仍然是原樹, 同時, 要求所加權值之和最小.
每次將兩個連通塊經過已有邊鏈接時, 須要將兩個連通塊中的點都連一條邊, 數量爲 \(size[a] * size[b] - 1\) ,同時知足所加邊權均爲 \(v + 1\) (同時知足權值最小和生成樹惟一).

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 6010;

struct Edge{
    int a, b, v;
    bool operator < (const Edge &t)const
    {
        return v < t.v;
    }
}e[N];
int fa[N];
int s[N];
int n;

int find(int x)
{
    if (x != fa[x])
        fa[x] = find(fa[x]);
    return fa[x];
}

int main()
{
    int t;
    cin >> t;
    while (t -- ){
        cin >> n;
        for (int i = 1; i < n; i ++ ){
            int a, b, v;
            cin >> a >> b >> v;
            e[i] = {a, b, v};
        }
        
        sort(e + 1, e + n);
        
        int res = 0;
        for (int i = 1; i <= n; i ++ ){
            fa[i] = i;
            s[i] = 1;
        }
        
        for (int i = 1; i < n; i ++ ){
            int a = find(e[i].a);
            int b = find(e[i].b);
            int v = e[i].v;
            
            if (a != b){
                res += (s[a] * s[b] - 1) * (v + 1); 
                s[b] += s[a];
                fa[a] = b;
            }
        }
        cout << res << endl;
    }
    
    return 0;
}
相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息