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
唯一真神。但是难写。