El pueblo unido jamas serà vencido!

[老题新解]LCT维护最小生成树

前言

之前见到的一道题,现在有了更优秀的解法。

题意

有一个 \(n\) 个点的无向图,初始没有边,每次加入一条带权边后询问最小生成树权值,无生成树输出 0,加边共计 \(m\) 次。
\(n \le 500,m \le 2000\)

解法

如果对于每次询问都直接做 Prim 最小生成树是可以通过这道题的,但是人要有追求,考虑如何通过更大的数据。

现在考虑求生成树的过程,当加入了一条边的时候有两种可能:

  1. 这条边连接的两点不在一棵树内,则这条边立即被选中
  2. 这条边连接的两点在一棵树内,需要分类讨论

现在着重讨论 2. 的情况,令这两个点为 \(u,v\),因为现在 \(u,v\) 在同一棵树上,那么 \(u,v\) 之间仅有一条简单路径,此时尝试用新的边替换掉 \(u,v\) 路径上边权最大的边,如果路径上边权都比新边小,那没有必要加入这条边。

于是我们需要实现动态加边,删边,查询路径最小值的数据结构,这里使用 Link-Cut Tree 维护。

但是由于 LCT 的辅助树结构本身不支持直接维护边权,我们把每条边作为一个点维护,边权就变成了点权。即对于加边 \((u,v)\),首先建立一个新的点 \(e\) 点权为 \(w(u,v)\),再连 \((e,u),(e,v)\) 两条边即可。

代码

#include <cstdio>
#include <cstring>
#include <algorithm>

constexpr int N = 50005;

struct LinkCutTree {
	bool rev[N];
	int fa[N],ch[N][2];
	int val[N],mx[N];

	#define ls(x) (ch[x][0])
	#define rs(x) (ch[x][1])
	#define dir(x) (x == ch[fa[x]][1])
	#define IsRoot(x) (x != ch[fa[x]][0] && x != ch[fa[x]][1])

	inline void PushUp(int x) {
		mx[x] = x;
		if(ls(x) && val[mx[ls(x)]] > val[mx[x]]) mx[x] = mx[ls(x)];
		if(rs(x) && val[mx[rs(x)]] > val[mx[x]]) mx[x] = mx[rs(x)];
	}

	inline void PushDown(int x) {
		if(rev[x]) {
			if(ls(x)) std::swap(ls(ls(x)),rs(ls(x))),rev[ls(x)] ^= 1;
			if(rs(x)) std::swap(ls(rs(x)),rs(rs(x))),rev[rs(x)] ^= 1;
			rev[x] = 0;
		}
	}

	void update(int x) {
		if(!IsRoot(x)) update(fa[x]);
		PushDown(x);
	}

	inline void rotate(int x) {
		int y = fa[x],z = fa[y],d = dir(x),w = ch[x][d ^ 1];
		if(!IsRoot(y)) ch[z][dir(y)] = x;
		ch[y][d] = w,ch[x][d ^ 1] = y;
		fa[y] = x,fa[x] = z;
		if(w) fa[w] = y;
		PushUp(y);
	}

	inline void splay(int x) {
		update(x);
		while(!IsRoot(x)) {
			int y = fa[x],z = fa[y];
			if(!IsRoot(y))
				rotate((ls(y) == x) ^ (ls(z) == y) ? x : y);
			rotate(x);
		}
		PushUp(x);
	}

	inline void access(int x) {
		for(int p = 0;x;p = x,x = fa[x])
			splay(x),rs(x) = p,PushUp(x);
	}

	inline void MakeRoot(int x) {
		access(x),splay(x);
		std::swap(ls(x),rs(x)),rev[x] ^= 1;
	}

	inline int FindRoot(int x) {
		access(x),splay(x);
		while(ls(x)) PushDown(x),x = ls(x);
		splay(x);
		return x;
	}

	inline void split(int x,int y) {
		MakeRoot(x),access(y),splay(y);
	}

	inline void link(int x,int y) {
		MakeRoot(x); fa[x] = y;
	}
}T;

int main() {
	int n,m,ecnt = 0;
	scanf("%d%d",&n,&m);
	int res = 0;
	for(int i = 1;i <= m;++i) {
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		if(u == v) continue;
		T.val[i + n] = w;
		T.MakeRoot(u);
		if(u != T.FindRoot(v)) {
			T.link(i + n,u),T.link(i + n,v);
			++ecnt;
			res += w;
		}
		else {
			T.split(u,v);
			int ep = T.mx[v];
			if(w < T.val[ep]) {
				T.splay(ep);
				T.fa[T.ch[ep][0]] = T.fa[T.ch[ep][1]] = 0;
				T.link(i + n,u);
				T.link(i + n,v);
				res -= T.val[ep];
				res += w;
			}
		}
		if(ecnt == n - 1) printf("%d\n",res);
		else puts("0");
	}
	return 0;
}

更多应用

最小差值生成树

求一个最小边权与最大边权差值最小的生成树。

首先将边排序,从小到大加边。每次选出链上最小边替换掉,然后更新最小差值。

严格次小生成树

求一个严格次于最小生成树的次小生成树权值。

首先建立最小生成树然后维护链上最大值和严格次大值,对于每条不被选中的边 \((u,v)\) 查询其替换掉最小生成树 \((u,v)\) 这条链上最大值的生成树权值,如果和最小生成树权值相等再尝试替换严格次大值。

魔法森林

一个无向图每条边边有两个权值 \((x,y)\),从一点 \(u\) 到另一点 \(v\) 需要的权值是路径上 \(\max\{ x \} + \max\{ y \}\),求从 \(1\)\(n\) 所需最小权值。

首先把边按照 \(x\) 权值升序排序,动态维护 \(y\) 权值的最小生成树,每次加边后如果 \(1,n\) 已经在一棵树内就更新最小值。

posted @ 2022-02-18 13:57  AstatineAi  阅读(678)  评论(0编辑  收藏  举报