MST

最小生成树(Minimum Spanning Tree)

无向连通图上选出一棵包含所有节点的树,使得边权之和最小。

非连通图上可以选出最小生成森林。

性质

MST 一定是瓶颈生成树(Bottleneck Spanning Tree)。

反证法,设最小生成树中的最大边权为 \(w\),如果最小生成树不是瓶颈生成树的话,则瓶颈生成树的所有边权都小于 \(w\),我们只需删去原最小生成树中的最长边,用瓶颈生成树中的一条边来连接删去边后形成的两棵树,得到的新生成树一定比原最小生成树的权值和还要小,这样就产生了矛盾。

求法

Kruskal

贪心地选取两端不在同一连通块的边,加入最小生成树边集,并合并两个连通块。用并查集维护。时间复杂度 \(O(m\log m+m\alpha(m,n))\)

void kruskal(){
    int ans=0,cnt=0;
    sort(E+1,E+m+1);
    for(int i=1;i<=m;i++){
        if(find(E[i].u)!=find(E[i].v)){
            ans+=E[i].w;
            cnt++;
            merge(E[i].u,E[i].v);
        }
    }
    if(cnt!=n-1){
        cerr<<"FKYOU\n";
    }else{
        cout<<ans<<'\n';
    }
}

Prim

比较少见,主要是在恶心卡常题而你又不会 Boruvka 时用。

从某个节点开始,贪心地选取一个距离最近的节点并扩展。用选择的边更新最短距离。朴素做法是 \(O(n^2+m)\) 的,类似 Dijkstra,用堆优化后复杂度为 \(O((n+m)\log n)\)

struct S {
  int u, d;
};
bool operator<(const S &x, const S &y) { return x.d > y.d; }
priority_queue<S> q;
void prim(){
    memset(dis, 0x3f, sizeof(dis));
    dis[1] = 0;
    q.push({1, 0});
    while (!q.empty()) {
        if (cnt >= n) break;
        int u = q.top().u, d = q.top().d;
        q.pop();
        if (vis[u]) continue;
        vis[u] = true;
        ++cnt;
        res += d;
        for (int i = h[u]; i; i = e[i].x) {
            int v = e[i].v, w = e[i].w;
            if (w < dis[v]) {
                dis[v] = w, q.push({v, w});
            }
        }
    }
}

Boruvka

唯一真神。但是难写。

posted @ 2024-10-30 09:33  view3937  阅读(13)  评论(0编辑  收藏  举报
Title