LCT

54 篇文章 0 订阅
4 篇文章 0 订阅

作用

一种动态树,用于维护森林,支持加边,删边,换根等操作,每次操作的复杂度均为log.

做法

将每棵树分成多条实链,实链之间用虚边连接,要求每一条实链不存在深度相同的点,之后每一条实链再用splay维护,并且维护时保证每个splay维护的子树的中序遍历深度递增,这样就可以动态地修改虚边和实边了.

核心操作:access

将某个点到根的路径变为实边,可以发现到根的路径将会经过多个splay,每次要将两个splay合并时都要将一条虚边改为实边,那么为了保证上述性质,可能要将一条实边换成虚边,每次将要连的点splay到实链的根后再修改,用循环来写还是比较短的.

inline void acc(int u)
{
	int p,q;
	for(p=u,q=0; p; q=p,p=fa[p])
	{
		splay(p);
		down(p);
		son[p][1]=q;
		up(p);
	}
}

make_root

若要将点p作为整棵树的根,首先access§,可以发现,此时p所在的实链中p是深度最大的点,因为中序遍历深度恰好为递增,若将中序遍历翻转也就是交换所有点的左右孩子,就可以将p变为深度最小的点,也就是整棵树的根,只要 s p l a y ( p ) splay(p) splay(p)后给p打上反转标记即可.

inline void make_root(int u)
{
	acc(u);
	splay(u);
	fz[u]^=1;
}

find_root

要找到p所在树的根只要先 a c c e s s ( p ) access(p) access(p) s p l a y ( p ) splay(p) splay(p),只要沿着p的左孩子一直找下去就是该树深度最小的一个点,也就是整棵树的根

inline int find_root(int u)
{
	acc(u),splay(u),down(u);
	for(; son[u][0]; u=son[u][0],down(u));
	return u;
}

需要注意的是这样find_root最劣会变成O(n)的,因此如果要判断两个点事否在同一个联通块中时,可以用如下代码

inline bool ask(int u,int v)
{
	make_root(u),make_root(v);
	for(;u;u=fa[u])
	{
		if(u==v) return 1;
	}
	return 0;
}

split

提取出u到v的链,只要将u作为根节点后,access(v),splay(v),那么就可以提取出这条链了.

inline void spl(int u,int v)
{
	make_root(u);
	acc(v),splay(v);
}

link

将u,v两条边连起来,首先用find_root是否相同来判断连通性,即是否要连边,之后不能直接该父节点,因为父节点记录了splay上的父节点,所以要将u变为根后再将fa[u]改为v.

inline void link(int u,int v)
{
	if(find_root(u)==find_root(v)) return;
	make_root(u),fa[u]=v;
}

cut

切断u,v间的边,首先split(u,v),提取出这条链,之后判断一下u是否恰好为v的左孩子,如果是,则修改u的父节点和v的左孩子即可.

inline void cut(int u,int v)
{
	spl(u,v);
	down(v),down(u);
	if(son[v][0]!=u || son[u][1]) return;
	fa[u]=son[v][0]=0;
	up(v);
}

代码(以P3690 【模板】Link Cut Tree (动态树)为例)

#include<iostream>
#include<cstdio>
#define N 300100
using namespace std;

int n,m,tt,dn[N],fa[N],son[N][2],xo[N];
bool fz[N];

inline bool as(int u){return son[fa[u]][1]==u;}
inline bool ar(int u){return son[fa[u]][0]!=u && son[fa[u]][1]!=u;}
inline void up(int u){if(u) xo[u]=xo[son[u][0]]^xo[son[u][1]]^dn[u];}
inline void down(int u)
{
	if(fz[u])
	{
		swap(son[u][0],son[u][1]);
		fz[son[u][0]]^=1;
		fz[son[u][1]]^=1;
		fz[u]=0;
	}
}

inline void rot(int u)
{
	down(fa[u]),down(u);
	int p=fa[u],d=as(u);
	if(!ar(p))
	{
		son[fa[p]][as(p)]=u;
	}
	fa[u]=fa[p];
	fa[p]=u;
	fa[son[u][!d]]=p;
	son[p][d]=son[u][!d];
	son[u][!d]=p;
	up(p),up(u);
}

inline void splay(int u)
{
	int p;
	for(; !ar(u);)
	{
		p=fa[u];
		if(!ar(p))
			as(u)==as(p)?rot(p):rot(u);
		rot(u);
	}
}

inline void acc(int u)
{
	int p,q;
	for(p=u,q=0; p; q=p,p=fa[p])
	{
		splay(p);
		down(p);
		son[p][1]=q;
		up(p);
	}
}

inline void make_root(int u)
{
	acc(u);
	splay(u);
	fz[u]^=1;
}

inline int find_root(int u)
{
	acc(u),splay(u),down(u);
	for(; son[u][0]; u=son[u][0],down(u));
	return u;
}

inline void spl(int u,int v)
{
	make_root(u);
	acc(v),splay(v);
}

inline void link(int u,int v)
{
	if(find_root(u)==find_root(v)) return;
	make_root(u),fa[u]=v;
}

inline void cut(int u,int v)
{
	spl(u,v);
	down(v),down(u);
	if(son[v][0]!=u || son[u][1]) return;
	fa[u]=son[v][0]=0;
	up(v);
}

int main()
{
	int i,j,o,p,q;
	cin>>n>>m;
	for(i=1; i<=n; i++)
	{
		scanf("%d",&dn[i]);
		xo[i]=dn[i];
	}
	for(i=1; i<=m; i++)
	{
		scanf("%d%d%d",&o,&p,&q);
		if(!o) spl(p,q),printf("%d\n",xo[q]);
		else if(o==1) link(p,q);
		else if(o==2) cut(p,q);
		else acc(p),splay(p),dn[p]=q,up(p);
	}
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值