Link-Cut-Tree详解

图片参考YangZhe的论文FlashHu大佬的博客

区别在于虚实是可以动态变化的,因此要使用更高级、更灵活的Splay来维护每一条由若干实边连接而成的实链

请先学习Splay之后再阅读本文

  • 查询、修改链上的信息(最值,总和等)

  • 随意指定原树的根(即换根)

  • 动态连边、删边

  • 动态维护连通性

  • 更多毒瘤操作

1.每一个Splay维护的是一条从上到下按在原树中深度严格递增的路径,且中序遍历Splay得到的每个点的深度序列严格递增

2.每个节点包含且仅包含于一个Splay中

3.边分为实边和虚边,实边包含在Splay中,而虚边总是由一棵Splay指向另一个节点(指向该Splay中中序遍历最靠前的点在原树中的父亲)

因为性质2,当某点在原树中有多个儿子时,只能向其中一个儿子拉一条实链(只认一个儿子),而其它儿子是不能在这个Splay中的

那么为了保持树的形状,我们要让到其它儿子的边变为虚边,由对应儿子所属的Splay的根节点的父亲指向该点,而从该点并不能直接访问该儿子(认父不认子)

一、access

假设有一珂树,有一棵树,一开始实边和虚边是这样划分的(虚线为虚边)

1309909-20180123095924037-1618037447.png

那么所构成的LCT可能会长这样(绿框中为一个Splay,可能不会长这样,但只要满足中序遍历按深度递增(性质1)就对结果无影响)

1309909-20180123095955350-1680422636.png

现在我们要access(N),把A~N的路径拉起来变成一条Splay

因为性质2,该路径上其它链都要给这条链让路,也就是把每个点到该路径以外的实边变虚

所以我们希望虚实边重新划分成这样

1309909-20180123101901740-2118178734.png

那么如何实现这个过程呢?

首先把splay(N),使之成为当前Splay中的根

为了满足性质2,原来N~O的重边要变轻

因为按深度O在N的下面,在Splay中O在N的右子树中,所以直接单方面将N的右儿子置为0(认父不认子)

然后就变成了这样——

1309909-20180123110136115-1112016464.png

我们接着把N所属Splay的虚边指向的I(在原树上是L的父亲)也转到它所属Splay的根,splay(I)

原来在I下方的重边I~K要变轻(同样是将右儿子去掉)

这时候I~L就可以变重了。因为L肯定是在I下方的(刚才L所属Splay指向了I),所以I的右儿子置为N,满足性质1。

1309909-20180123110156272-1242463729.png

或许看了这些聪明的你就能发现规律

剩下的步骤自己脑补

想使一个点到根之间的路径在同一个Splay中只需要循环执行以下操作:

1.转到根
2.换儿子
3.跟新
4.当前操作点切换为轻边所指的父亲
	inline void pushup(register int x)
	{
		xr[x]=xr[c[x][0]]^xr[c[x][1]]^val[x];
	}
	inline void pushdown(register int x){
		if(rev[x])
		{
			register int l=c[x][0],r=c[x][1];
			rev[l]^=1,rev[r]^=1,rev[x]^=1;
			Swap(c[x][0],c[x][1]);
		}
	}
	inline bool isroot(register int x)
	{
		return c[fa[x]][0]!=x&&c[fa[x]][1]!=x;
	}
	inline void rotate(register int x)
	{
		int y=fa[x],z=fa[y],l,r;
		l=c[y][0]==x?0:1;
		r=l^1;
		if(!isroot(y))
			c[z][c[z][0]==y?0:1]=x;
		fa[x]=z;
		fa[y]=x;
		fa[c[x][r]]=y;
		c[y][l]=c[x][r];
		c[x][r]=y;
		pushup(y),pushup(x);
	}
	inline void splay(register int x)
	{
		top=1;
		q[top]=x;
		for(register int i=x;!isroot(i);i=fa[i])
			q[++top]=fa[i];
		for(register int i=top;i;--i)
			pushdown(q[i]);
		while(!isroot(x))
		{
			int y=fa[x],z=fa[y];
			if(!isroot(y))
				rotate((c[y][0]==x)^(c[z][0]==y)?(x):(y));
			rotate(x);
		}
	}
	inline void access(register int x)
	{
		for(register int t=0;x;t=x,x=fa[x])
		{
			splay(x);
			c[x][1]=t;
			pushup(x);
		}
	}

pushdown就跟懒标记差不多(珂以先不看)

二、makeroot

makeroot定义为换根,让指定点成为原树的根

这时候就利用到access(x)和Splay的翻转操作

access(x)后x在Splay中一定是深度最大的点。

splay(x)后,x在Splay中将没有右子树(性质1)。于是翻转整个Splay,使得所有点的深度都倒过来了,x没了左子树,反倒成了深度最小的点(根节点),达到了我们的目的

	inline void makeroot(register int x)
	{
		access(x);
		splay(x);
		rev[x]^=1;
	}

三、findroot

找x所在原树的树根,主要用来判断两点之间的连通性(findroot(x)==findroot(y)表明x,y在同一棵树中)

inline int findroot(register int x)
	{
		access(x);
		splay(x);
		while(c[x][0])
			x=c[x][0];
		return x;
	}

在x,y两点之间连边

只在保证题目数据合法的情况下才能使用(不一定合法的话先要判联通(findroot))

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

五、cut

将x,y之间的边切断

	inline void split(register int x,register int y)
	{
		makeroot(x);
		access(y);
		splay(y);
	}
	inline void cut(register int x,register int y)
	{
		split(x,y);
		if(c[y][0]==x)
		{
			c[y][0]=0;
			fa[x]=0;
		}
	}

完整代码

#include <bits/stdc++.h>
#define N 300005
#define getchar nc
using namespace std;
inline char nc(){
    static char buf[100000],*p1=buf,*p2=buf; 
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++; 
}
inline int read()
{
    register int x=0,f=1;register char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return x*f;
}
inline void write(register int x)
{
    if(!x)putchar('0');if(x<0)x=-x,putchar('-');
    static int sta[20];register int tot=0;
    while(x)sta[tot++]=x%10,x/=10;
    while(tot)putchar(sta[--tot]+48);
}
inline void Swap(register int &a,register int &b)
{
    a^=b^=a^=b;
}
int n,m,val[N];
struct Link_Cut_Tree{
	int c[N][2],fa[N],top,q[N],xr[N],rev[N];
	inline void pushup(register int x)
	{
		xr[x]=xr[c[x][0]]^xr[c[x][1]]^val[x];
	}
	inline void pushdown(register int x){
		if(rev[x])
		{
			register int l=c[x][0],r=c[x][1];
			rev[l]^=1,rev[r]^=1,rev[x]^=1;
			Swap(c[x][0],c[x][1]);
		}
	}
	inline bool isroot(register int x)
	{
		return c[fa[x]][0]!=x&&c[fa[x]][1]!=x;
	}
	inline void rotate(register int x)
	{
		int y=fa[x],z=fa[y],l,r;
		l=c[y][0]==x?0:1;
		r=l^1;
		if(!isroot(y))
			c[z][c[z][0]==y?0:1]=x;
		fa[x]=z;
		fa[y]=x;
		fa[c[x][r]]=y;
		c[y][l]=c[x][r];
		c[x][r]=y;
		pushup(y),pushup(x);
	}
	inline void splay(register int x)
	{
		top=1;
		q[top]=x;
		for(register int i=x;!isroot(i);i=fa[i])
			q[++top]=fa[i];
		for(register int i=top;i;--i)
			pushdown(q[i]);
		while(!isroot(x))
		{
			int y=fa[x],z=fa[y];
			if(!isroot(y))
				rotate((c[y][0]==x)^(c[z][0]==y)?(x):(y));
			rotate(x);
		}
	}
	inline void access(register int x)
	{
		for(register int t=0;x;t=x,x=fa[x])
		{
			splay(x);
			c[x][1]=t;
			pushup(x);
		}
	}
	inline void makeroot(register int x)
	{
		access(x);
		splay(x);
		rev[x]^=1;
	}
	inline int findroot(register int x)
	{
		access(x);
		splay(x);
		while(c[x][0])
			x=c[x][0];
		return x;
	}
	inline void split(register int x,register int y)
	{
		makeroot(x);
		access(y);
		splay(y);
	}
	inline void cut(register int x,register int y)
	{
		split(x,y);
		if(c[y][0]==x)
		{
			c[y][0]=0;
			fa[x]=0;
		}
	}
	inline void link(register int x,register int y)
	{
		makeroot(x);
		fa[x]=y;
	}
}T; 
int main()
{
	n=read(),m=read();
	for(register int i=1;i<=n;++i)
	{
		val[i]=read();
		T.xr[i]=val[i];
	}
	while(m--)
	{
		int opt=read();
		if(opt==0)
		{
			int x=read(),y=read();
			T.split(x,y);
			write(T.xr[y]),puts("");
		}
		else if(opt==1)
		{
			int x=read(),y=read();
			if(T.findroot(x)!=T.findroot(y))
				T.link(x,y);
		}
		else if(opt==2)
		{
			int x=read(),y=read();
			T.cut(x,y);
		}
		else
		{
			int x=read(),y=read();
			T.access(x);
			T.splay(x);
			val[x]=y;
			T.pushup(x);
		}
	}
	return 0;
} 
posted @ 2018-12-31 14:50  JSOI爆零珂学家yzhang  阅读(3537)  评论(2编辑  收藏  举报