LCT浅(糙)谈

1.引出问题

ACwing上的模板题(没必要上Luogu)

动态树问题,要支持加/删边,和查询/修改操作。如果树边没有改变,可以用树链剖分解决。怎么转到动态树呢?实链剖分,实儿子没有什么实际含义,灵活可变。我们可以用Splay维护这些实链。

2.辅助树

1.每一个Splay维护一个实链的信息。

2.原树与辅助树的节点对应。

3.在LCT中每棵Splay的根节点的父亲节点指向原树中这条链的父亲节点。

4.认父不认子,很重要的性质,保证辅助树分为多颗Splay。父亲找不到虚儿子。

5.只用考虑辅助树,原树没用。

3.具体操作

下面抄自OI Wiki(代码质量不好)FlashHu的博客(WC讲课老师推荐)

为保证时间复杂度,要在改变辅助树后Splay。

函数顺序大致参考WC讲课老师金靖的PPT(害怕侵权,不作上传)。

I.Splay原有操作

1.splay

与一般Splay不同,这里要判断x的父亲是否为根。还要更新下传根到点的所有懒标记。(见update操作)

void splay(int x)
{
	update(x);
	for(int y;y=fa[x],!isroot(x);rotate(x))
	{
		if(!isroot(y))
		{
			rotate(get(x)==get(y)?y:x);
		}
	}
	pushup(x);
}

2.rotate

也要判父亲是否为根。

void splay(int x)
{
	update(x);
	for(int y;y=fa[x],!isroot(x);rotate(x))
	{
		if(!isroot(y))
		{
			rotate(get(x)==get(y)?y:x);
		}
	}
	pushup(x);
}

II.LCT特有操作

1.Access(x)

x到原树根变成实链,某些边受影响可能变虚边。

(所有操作的基础,LCT的关键)

统共4步:

将当前节点转到根上,Splay基操;之前节点变成右儿子(路径放到实链上);更新;转到父亲,继续第1步。

这样,和父亲的边就在实链上了。同时,由于一直放在右儿子,x最终会变成Splay中最右的点。

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

2.makeroot

将x变为原树的根

通过access让x和原树的根在同一颗Splay中,子孙都要左右翻转(打懒标记)。

HQX解释:x在access后成为Splay最右的点(中序遍历最后),翻转整个Splay(每层都要翻转),x成为成了最左的点(深度最小)。

由于放到根上比较特殊,也很好做,所以这个函数可以大大地帮助我们实现其他操作。

void makeroot(int x)
{
	access(x);
	splay(x);
	rev(x);
}

3.findroot

找x所在原树的根。

把x与树根放在同一条链,维护Splay,找最左的点(根),记得下传标记。(同上HQX所述)

int findroot(int x)
{
	access(x);
	splay(x);
	while(ch[x][0]){
		pushdown(x);
		x=ch[x][0];
	}
	splay(x);
	return x;
}

4.split

将x到y的路径整出来

把x变为原树的根,让y与原树根(x)在同一条链,y变为Splay的根,所在Splay包含x到y路径。

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

5.link

把x和y连边

把x变为原树的根,利用根无父亲的特点,让y成为x的父亲,完成连边。

记得在makeroot后,判断x和y是否在同一颗原树内,防止非法连边破坏树的结构。

void link(int x,int y)
{
	makeroot(x);
	int z=findroot(y);
	if(z!=x)fa[x]=y;
}

6.cut

把x和y连的边删掉

把x变为原树的根,y只能为x的右儿子(根深度最浅,没有左儿子即更浅的点),直接断开。

记得在makeroot后,判断x和y是否在同一颗原树内,防止断不存在的边。

void cut(int x,int y)
{
	makeroot(x);
	int z=findroot(y);
	if(z==x&&fa[y]==x&&!ch[y][0])
	{
		fa[y]=ch[x][1]=0;
		pushup(x);
	}
}

7.reverse

void rev(int x)
{
	swap(ch[x][0],ch[x][1]);
	tag[x]^=1; 
}

翻转操作

左右儿子翻转,给当前点更改懒标记。

8.update

更新下传根到点的所有懒标记

顺序绝对不能搞反,通过递归实现父亲先pushdown。

void update(int x)
{
	if(!isroot(x))update(fa[x]);
	pushdown(x);
}

III.具体操作

1.删/加边:cut/link(x,y)

2.x到y路径异或和:split(x,y),答案在y上。

3.修改x的权值:splay(x),x变为Splay根节点,再修改x,避免对其他点造成影响。

#include<bits/stdc++.h>
#define mid ((l+r)>>1)
#define inf 1000000007
using namespace std;
int n,m,a[1000005];
long long read()
{
	long long x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+ch-48,ch=getchar();
	return x*f;
}
struct LCT
{
	
	int next[1000005],to[1000005],h[1000005],cnt;
	int fa[1000005],ch[1000005][2],sz[1000005],tag[1000005];
	int s[1000005],v[1000005];
	void pushup(int x)
	{
		sz[x]=sz[ch[x][0]]+sz[ch[x][1]];
		s[x]=s[ch[x][0]]^s[ch[x][1]]^v[x];
	}
	void rev(int x)
	{
		swap(ch[x][0],ch[x][1]);
		tag[x]^=1; 
	}
	void pushdown(int x)
	{
		if(!tag[x])return;
		if(ch[x][0])rev(ch[x][0]);
		if(ch[x][1])rev(ch[x][1]);
		tag[x]=0;
	}
	bool isroot(int x)
	{
		return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;
	}
	int get(int x){return x==ch[fa[x]][1];}
	void rotate(int x)
	{
		int y=fa[x],z=fa[y],k=get(x),kk=get(y);
		if(!isroot(y))ch[z][kk]=x;
		ch[y][k]=ch[x][k^1];
		fa[ch[x][k^1]]=y;
		ch[x][k^1]=y;
		fa[y]=x;fa[x]=z;
		pushup(y);pushup(x);
	}
	void update(int x)
	{
		if(!isroot(x))update(fa[x]);
		pushdown(x);
	}
	void splay(int x)
	{
		update(x);
		for(int y;y=fa[x],!isroot(x);rotate(x))
		{
			if(!isroot(y))
			{
				rotate(get(x)==get(y)?y:x);
			}
		}
		pushup(x);
	}
	void access(int x)
	{
		int y;
		for(y=0;x;x=fa[y=x])
		{
			splay(x),ch[x][1]=y,pushup(x);
		}
		//return y;
	}
	void makeroot(int x)
	{
		access(x);
		splay(x);
		rev(x);
	}
	int findroot(int x)
	{
		access(x);
		splay(x);
		while(ch[x][0]){
			pushdown(x);
			x=ch[x][0];
		}
		splay(x);
		return x;
	}
	void split(int x,int y)
	{
		makeroot(x);
		access(y);
		splay(y); 
	}
	void link(int x,int y)
	{
		makeroot(x);
		int z=findroot(y);
		if(z!=x)fa[x]=y;
	}
	void cut(int x,int y)
	{
		makeroot(x);
		int z=findroot(y);
		if(z==x&&fa[y]==x&&!ch[y][0])
		{
			fa[y]=ch[x][1]=0;
			pushup(x);
		}
	} 
}t;
int main()
{
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	n=read(),m=read();
	for(int i=1;i<=n;i++)
	{
		t.v[i]=read();
	}
	for(int i=1,opt,x,y;i<=m;i++)
	{
		opt=read(),x=read(),y=read();
		if(opt==0)
		{
			t.split(x,y);
			printf("%d\n",t.s[y]);
		}
		if(opt==1)
		{
			t.link(x,y);
		}
		if(opt==2)
		{
			t.cut(x,y);
		}
		if(opt==3)
		{
			t.splay(x);
			t.v[x]=y;
			t.pushup(x);
		}
	}
	return 0;
}

4.写在最后

以前总觉得LCT是难以企及的高峰,是洪水猛兽。

但现在仔细钻研,回头再看,并没有那么难。

模板题只有2k+bytes,思路也很明了。

(但是非模板题都很难)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值