最小生成樹MST的三個簡單應用

kruskal算法相當簡單直觀~還比prim好用~

應用一, 求最大權值最小的最小生成樹.

仔細想一想, 最小生成樹的最大權值就是最小的.

應用二, 求次小生成樹.

先求一個最小生成樹, 然後枚舉這個最小生成樹的每一條邊, 然後計算分別刪去這個邊的圖的最小生成樹, 一共有n - 1個, 次小生成樹一定是這幾個.

poj1679的問圖的最小生成樹是否爲一, 可以先求最小生成樹, 在求次小生成樹, 如果兩個生成樹的權值相等, 則說明最小生成樹不唯一.

求次小生成樹的優化:

引入概念:

若 T 是圖 G 的一個生成樹, 對於非樹邊 a 和樹邊 b , 插入邊 a 並刪除邊 b 的操作記爲(+a, -b).

如果 T + a - b 仍然是一個生成樹, 稱(+a, -b)是 T 的一個可行交換.

設T爲圖G的一個生成樹, 由T進行一個可行交換得到的新的生成樹集合稱爲T的鄰集.

定理: 次小生成樹一定在最小生成樹的鄰集中.

優化方法:

1. 通過O(n^2)求出MAX[i][j]數組, 代表着i到j之間的路徑上的最大邊權.

2. 通過倍增思想, 在O(elge)的時間內求出MAX[i][j], 代表着i到i的2^j輩祖先路徑的最大邊權.再通過LCA可以求出任意兩個節點路徑之間的最大邊權了.

#include <iostream>
#include <algorithm>
#include <vector>
#include <set>
#include <cstdlib>
#include <cstdio>
#include <cmath>
using namespace std;
const int MAXN = 120;
const int MAXM = 5500;
int faz[MAXN];
int n, m;
struct Edge{
    int from, to, weight;
    Edge(int from, int to, int weight): from(from), to(to), weight(weight){}
    bool operator<(const Edge & rhs) const{
        return weight < rhs.weight;
    }
};
int find(int x){
    if(x == faz[x]){
        return x;
    }
    return faz[x] = find(faz[x]);
}
void merge(int x, int y){
    x = find(x);
    y = find(y);
    faz[x] = y;
}
void init(){
    for(int i = 0; i < MAXN; ++i){
        faz[i] = i;
    }
}
int main(){
    int T;
    scanf("%d", &T);
    while(T--){
        scanf("%d%d", &n, &m);
        int x, y, w;
        vector<Edge> v;
        for(int i = 0; i < m; ++i){
            scanf("%d%d%d", &x, &y, &w);
            v.push_back(Edge(x, y, w));
        }
        sort(v.begin(), v.end());
        init();
        vector<int> flag;
        int mst_cost = 0;
        for(int i = 0; i < v.size(); ++i){
            int x = v[i].from, y = v[i].to;
            if(find(x) != find(y)){
                merge(x, y);
                mst_cost += v[i].weight;
                flag.push_back(i);
            }
        }
        int second_cost = 0x3f3f3f3f;
        for(int i = 0; i < flag.size(); ++i){
            int cur_cost = 0;
            init();
            for(int j = 0; j < v.size(); ++j){
                if(flag[i] != j){
                    int x = v[j].from, y = v[j].to, w = v[j].weight;
                    if(find(x) != find(y)){
                        merge(x, y);
                        cur_cost += w;
                    }
                }
            }
            int chief = find(1);
            for(int i = 2; i <= n; ++i){
                if(find(i) != chief){
                    cur_cost = 0x3f3f3f3f;
                }
            }
            second_cost = min(second_cost, cur_cost);
        }
        if(mst_cost == second_cost){
            printf("Not Unique!\n");
        }
        else{
            printf("%d\n", mst_cost);
        }
    }
    return 0;
}

應用三, 求最大權值和最小權值的差最小的生成樹.

先求最小生成樹, 然後把這個生成樹中的最小邊從圖中刪除, 在求一次最小生成樹, 再把這個生成樹的最小邊從圖中刪除...重複這個過程, 刪去的邊不能恢復.

這其中一定有一顆最大權值和最小權值的差最小的生成樹, 這很像尺取, 反過來求最大生成樹也應該可以.

注意這裏算法的終止條件: 當剩餘的邊不足以使點聯通了.

#include <iostream>
#include <algorithm>
#include <vector>
#include <set>
#include <cstdlib>
#include <cstdio>
#include <cmath>
using namespace std;
const int MAXN = 120;
const int MAXM = 5500;
int faz[MAXN];
struct Edge{
    int from, to, limit;
    Edge(int from, int to, int limit): from(from), to(to), limit(limit){}
    bool operator<(const Edge & rhs) const{
        return limit < rhs.limit;
    }
};
int find(int x){
    int root = x;
    while(root != faz[root]){
        root = faz[root];
    }
    int temp;
    while(x != root){
        temp = faz[x];
        faz[x] = root;
        x = temp;
    }
    return root;
}
void merge(int x, int y){
    x = find(x);
    y = find(y);
    faz[x] = y;
}
void init(){
    for(int i = 0; i < MAXN; ++i){
        faz[i] = i;
    }
}
int main(){
    int n, m;
    int x, y, l;
    scanf("%d%d", &n, &m);
    int rst = 0x3f3f3f3f;
    vector<Edge> v;
    for(int i = 0; i < m; ++i){
        scanf("%d%d%d", &x, &y, &l);
        v.push_back(Edge(x, y, l));
    }
    sort(v.begin(), v.end());
    int maxn = -0x3f3f3f3f, minn = 0x3f3f3f3f;
    vector<int> smallest;
    int counter = 0;
    init();
    bool trigger = true;
    for(int i = 0; i < v.size(); ++i){
        int x = v[i].from, y = v[i].to, l = v[i].limit;
        if(find(x) != find(y)){
            merge(x, y);
            ++counter;
            maxn = max(maxn, l);
            minn = min(minn, l);
            if(trigger){
            	smallest.push_back(i);
                trigger = false;
            }
        }
    }
    if(counter != n - 1){
        printf("-1\n");
        return 0;
    }
    rst = min(rst, maxn - minn);
    while(true){// 枚舉最小邊
        init();
        int counter = 0;
        maxn = -0x3f3f3f3f, minn = 0x3f3f3f3f;
        trigger = true;
        for(int i = 0; i < v.size(); ++i){
            if(lower_bound(smallest.begin(), smallest.end(), i) != smallest.end()){
                continue;
            }
            int x = v[i].from, y = v[i].to, l = v[i].limit;
            if(find(x) != find(y)){
                merge(x, y);
                ++counter;
                maxn = max(maxn, l);
                minn = min(minn, l);
                if(trigger){
                    smallest.push_back(i);
                    trigger = false;
                }
            }
        }
        if(counter == n - 1){
            rst = min(rst, maxn - minn);
        }
        else{
            break;
        }
    }
    printf("%d\n", rst);
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章