【LCT】动态树基础

树链剖分可以解决很多与链有关的问题。可是,如果我们维护的树有变化,树链剖分便黯然失色。这时候,LCT就派上用场了。LCT是一种神奇的数据结构。它的主要思路就是用多颗splay来维护一棵树的若干子树。(或者维护森林)
下面从几个方面介绍一下LCT:

1.轻边与重边
与树链剖分类似,LCT也把边分为轻边和重边。而是否是重边,是根据splay中的父亲确定的。每一颗splay中,对于一个节点,它的儿子节点与它相连的边就是重边。因此,重边是双向的——即既可以从儿子找父亲,也可以从父亲找儿子。而轻边则可以理解为单向的,只可以通过儿子的fa数组找到父亲,而父亲处却不包含这个儿子的信息。显而易见,对于一个重边构成的联通块,就是一颗splay所维护的子树。

2.splay与原图的关系
事实上,由于splay的伸展操作,我们维护的树的样子可能不同于原树的样子。因此,我们写代码时必须明白splay的中序遍历是它所维护的子树的中序遍历。fa(父亲数组),ch(儿子数组)都记录的是splay中的父子关系,而每一棵splay根的父亲代表了当前splay联通块与其“父联通块”所连接的的边,但是必须注意,splay根的父亲代表的是联通块之间的关系,所以原树中可能并不存在这条边。
因此,我们用如下代码判断splay的根。

	inline bool isroot(int x)
	{
		return !fa[x]||(x!=ch[fa[x]][0]&&x!=ch[fa[x]][1]);
	}

3.access
这个操作的主要目的是将一个节点到根的路径上的边全部变为重边。这个操作的实现很简单,每到达一个splay联通块不断向父联通块走,路上不断将经过的splay联通块之间的边变为重边,直到走到根。

	inline void access(int x)
	{
		for(int re y=0;x;y=x,x=fa[x])
			splay(x),ch[x][1]=y,pushup(x);
	}

由于splay的个数比较稳定,因此这个操作的复杂度是可以得到保证的。这样以后,当前节点就与根在同一颗splay中,而路径信息也就相应地保存在了这一颗splay中,方便询问等操作。

4.makeroot(换根操作)
需要注意的是,这个换根是在换原树的根,而不是splay的根。实现很简单,把当前节点和根连接起来,接着splay当前节点到根,然后交换左右儿子,打上翻转标记(这个有点类似文艺平衡树的区间翻转,这里相当于翻转中根序达到换根的目的)。

	inline void pushnow(int u)
	{
		laz[u]^=1;
		swap(ch[u][1],ch[u][0]);
	}
	inline void makert(int x)
	{
		access(x);
		splay(x);
		pushnow(x);
	}

5.splay(伸展操作)
需要注意的是,这个是在换splay的根。与通的splay不同的是,我们一般是从儿子找父亲,而普通的splay是从父亲找儿子。因此,我们必须先找到当前splay的根,以防止标记没有下传。

	inline void rotate(int x)
	{
		int y=fa[x],z=fa[y],d=get(x);
		if(!isroot(y))ch[z][get(y)]=x;
		fa[x]=z;fa[y]=x;ch[y][d]=ch[x][d^1];
		ch[x][d^1]=y;fa[ch[y][d]]=y;
		pushup(y),pushup(x);
	}
	inline void splay(int u)
	{
		top=0;
		s[++top]=u;
		for(int re i=u;!isroot(i);i=fa[i])s[++top]=fa[i];
		while(top)pushdown(s[top--]);
		for(int re y=fa[u];!isroot(u);rotate(u),y=fa[u])
			if(!isroot(y))rotate(get(u)==get(y)?y:u);
	}

6.split(将两个节点x,y之间的路径变为重路径)
这个操作主要是为了链查询和链修改。实现很简单,把x变为原树的根,接着access(y),此时便将x,y的路径变为重路径,我们再把y splay到根,这样y就拥有了整条路径的信息。

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

7.find
这个操作是为了找到某个节点所在树的根节点。(原树中的根节点)(一般维护森林时需要用find判断连通性)

	inline int find(int u)
	{
		access(u);splay(u);
		while(ch[u][0])pushdown(u),u=ch[u][0];
		splay(u);return u;
	}

8.link,cut
都属于比较简单的操作,目的是连边和删边。需要注意的是,link里面连接的是轻边,不需要pushup,由于有些链修改的题需要打标记,如果pushup可能会导致当前节点标记未下传而pushup,如果儿子信息尚未更新,会使当前节点的信息变回原来修改之前的信息。最好在pushup里加pushdown处理这个问题。

	inline void link(int x,int y)
	{
		makert(x);fa[x]=y;
	}
	inline void cut(int x,int y)
	{
		if(find(x)!=find(y))return;//如果两者之间没有边,返回
		split(x,y);
		if(fa[x]!=y || ch[x][1])return;//如果两者之间没有边或者两者之间还有其它节点,返回
		ch[y][0]=fa[x]=0;pushup(y);
	}

【例题】【模板】LCT(动态树)
【题目描述】
给定n个点以及每个点的权值,要你处理接下来的m个操作。操作有4种。操作从0到3编号。点从1到n编号。

0:后接两个整数(x,y),代表询问从x到y的路径上的点的权值的xor和。保证x到y是联通的。

1:后接两个整数(x,y),代表连接x到y,若x到y已经联通则无需连接。

2:后接两个整数(x,y),代表删除边(x,y),不保证边(x,y)存在。

3:后接两个整数(x,y),代表将点x上的权值变成y。

【输入】
第1行两个整数,分别为n和m,代表点数和操作数。

第2行到第n+1行,每行一个整数,整数在[1,10^9]内,代表每个点的权值。

第n+2行到第n+m+1行,每行三个整数,分别代表操作类型和操作所需的量。

【输出】
对于每一个0号操作,你须输出x到y的路径上点权的xor和。

样例输入
3 3
1
2
3
1 1 2
0 1 2
0 1 1
样例输出
3
1
完整代码:

#include<bits/stdc++.h>
#define re register
using namespace std;
int n,m,a,b,c;
inline int red()
{
    int data=0;int w=1; char ch=0;
    ch=getchar();
    while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
    return data*w;
}
const int N=3e5+5;
struct lct{
	int ch[N][2],fa[N],sum[N],val[N],laz[N];
	int top,s[N];
	int siz[N];
	inline void pushnow(int u)
	{
		laz[u]^=1;
		swap(ch[u][1],ch[u][0]);
	}
	inline int get(int x){return x==ch[fa[x]][1];}
	inline void pushdown(int u)
	{
		if(!laz[u])return;
		pushnow(ch[u][1]);
		pushnow(ch[u][0]);
		laz[u]=0;
	}
	inline void pushup(int u)
	{
		sum[u]=sum[ch[u][1]]^sum[ch[u][0]]^val[u];
		siz[u]=siz[ch[u][1]]+siz[ch[u][0]]+1;
	}
	inline bool isroot(int x){return !fa[x]||(x!=ch[fa[x]][0]&&x!=ch[fa[x]][1]);}
	inline void rotate(int x)
	{
		int y=fa[x],z=fa[y],d=get(x);
		if(!isroot(y))ch[z][get(y)]=x;
		fa[x]=z;fa[y]=x;ch[y][d]=ch[x][d^1];
		ch[x][d^1]=y;fa[ch[y][d]]=y;
		pushup(y),pushup(x);
	}
	inline void splay(int u)
	{
		top=0;
		s[++top]=u;
		for(int re i=u;!isroot(i);i=fa[i])s[++top]=fa[i];
		while(top)pushdown(s[top--]);
		for(int re y=fa[u];!isroot(u);rotate(u),y=fa[u])
			if(!isroot(y))rotate(get(u)==get(y)?y:u);
	}
	inline void access(int x)
	{
		for(int re y=0;x;y=x,x=fa[x])
			splay(x),ch[x][1]=y,pushup(x);
	}
	inline void makert(int x){access(x);splay(x);pushnow(x);}
	inline int find(int u)
	{
		access(u);splay(u);
		while(ch[u][0])pushdown(u),u=ch[u][0];
		splay(u);return u;
	}
	inline void split(int x,int y)
	{
		makert(x);access(y);splay(y);
	}
	inline void link(int x,int y)
	{
		makert(x);fa[x]=y;
	}
	inline void cut(int x,int y)
	{
		if(find(x)!=find(y))return;
		split(x,y);
		if(fa[x]!=y || ch[x][1])return;
		ch[y][0]=fa[x]=0;pushup(y);
	}
	inline int query(int x,int y)
	{
		split(x,y);return sum[y];
	}
}lct;
int main()
{
	scanf("%d%d",&n,&m);
	for(int re i=1;i<=n;i++)lct.val[i]=red();
	while(m--)
	{
		int opt=red(),x=red(),y=red();
		if(opt==0)
			printf("%d\n",lct.query(x,y));
		else if(opt==1)
			{
				if(lct.find(x)!=lct.find(y))
					lct.link(x,y);
			}
		else if(opt==2)lct.cut(x,y);
		else
		{
			lct.access(x);lct.splay(x);lct.val[x]=y;lct.pushup(x);
		}
	}
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值