简介

本文将简单介绍如何使用 Link Cut Tree 维护动态图最小生成树。

思路

最小生成树的性质:一个基环树的最小生成树,为将环上边权最大的边删除后所组成的树。

Proof:如果删除环上的其他边,那么删除的边的权一定不大于最大边的边权。所以删最大权的边的树的边权和比其他的都要小。符合最小生成树定义。

如果我们插入边 \((u,v,w)\)。先判断 \(u,v\) 之间是否连通(这个可以简单的用 LCT 完成,不会的去做 P2147 [SDOI2008] 洞穴勘测)。

  • 如果不连通,那么就最小生成树上(就是 LCT 上)连边 \((u,v)\)
  • 如果连通,那么先找到路径 \((u,v)\) 上边权最大的边,如果它的边权小于等于 \(w\),那么我们要插入的边是“废边”,直接忽略。否则将边权最大的边 Cut 掉,连上 \((u,v,w)\)

注意到 LCT 无法直接维护边权,于是我们可以将边拆点之后维护(如果不会去做 SPOJ QTREE - Query on a tree)。

至此,以上操作全部可以用 LCT 完成(只需要维护最大值和最大值位置)。时间复杂度单次期望 \(O(\log n)\)

代码

这里以 P3366 【模板】最小生成树 为例。

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 4e5+5;

namespace LCT{
#define ls (son[i][0])
#define rs (son[i][1])
	int son[N][2];
	int fa[N];
	bool tag[N];
	int maxt[N],maxid[N];
	int val[N];
	
	inline void pushup(int i){
		maxt[i]=val[i],maxid[i]=i;
		if(maxt[ls]>maxt[i]){
			maxt[i]=maxt[ls];maxid[i]=maxid[ls];
		}
		if(maxt[rs]>maxt[i]){
			maxt[i]=maxt[rs];maxid[i]=maxid[rs];
		}
	}
	
	inline void reverse(int i){
		swap(ls,rs);tag[i]^=1;
	}
	
	inline void pushdown(int i){
		if(tag[i]){
			if(ls) reverse(ls);
			if(rs) reverse(rs);
			tag[i]=0;
		}
	}
	
	inline bool get(int i){
		return son[fa[i]][1]==i;
	}
	
	inline bool is_root(int i){
		return son[fa[i]][0]!=i && son[fa[i]][1]!=i;
	}
	
	void update(int i){
		if(!is_root(i)){
			update(fa[i]);
		}
		pushdown(i);
	}
	
	inline void rotate(int p){
		int q=fa[p],z=fa[q],k=get(p);
		if(!is_root(q)){
			son[z][son[z][1]==q]=p;
		}
		fa[p]=z;
		son[q][k]=son[p][!k];
		if(son[p][!k]) fa[son[p][!k]]=q;
		son[p][!k]=q;
		fa[q]=p;
		pushup(q);
		pushup(p);
	}
	
	inline void splay(int i){
		update(i);
		for(int f;f=fa[i],!is_root(i);rotate(i)){
			if(!is_root(f)){
				rotate(get(f)==get(i)?f:i);
			}
		}
	}
	
	inline void access(int i){
		int p;
		for(p=0;i;p=i,i=fa[i]){
			splay(i);
			son[i][1]=p;
			pushup(i);
		}
	}
	
	inline int find(int i){
		access(i);
		splay(i);
		while(ls) pushdown(i),i=ls;
		splay(i);
		return i;
	}
	
	inline void make_root(int i){
		access(i);
		splay(i);
		reverse(i);
	}
	
	inline void split(int u,int v){
		make_root(u);
		access(v);splay(v);
	}
	
	inline void link(int u,int v){
		make_root(u);
		if(find(v)!=u){
			fa[u]=v;
		}
	}

	inline void cut(int i){
		splay(i);
		fa[ls]=fa[rs]=0;
	}
}

int ret=0,ec=0;

signed main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int u,v,w;
		cin>>u>>v>>w;
		LCT::val[i+n]=w;
		if(LCT::find(u)!=LCT::find(v)){
			LCT::link(u,i+n);LCT::link(i+n,v);
			ret += w;
			ec++;
			continue;
		}
		LCT::split(u,v);
		int mxid=LCT::maxid[v],mxv=LCT::maxt[v];
		if(mxv<=w) continue;
		ret -= mxv;
		LCT::cut(mxid);
		LCT::link(u,i+n);
		LCT::link(i+n,v);
		ret += w;
	}
	if(ec==(n-1)) cout<<ret;
	else cout<<"orz";
	return 0;
}

习题

P2387 [NOI2014] 魔法森林