• 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,思路也很明了。

    (但是非模板题都很难)

  • 相关阅读:
    关键字与标识符
    JAVA 程序的基本语法
    第一个java程序中文乱码以及如何解决
    第一个java程序以及java的运行机制
    java中求余%与取模floorMod的区别
    Volley 框架解析(二)--RequestQueue核心解读
    Volley 源码解析
    Android之Activity系列总结(三)--Activity的四种启动模式
    Android之Activity系列总结(二)--任务和返回栈
    Android之Activity系列总结(一)--Activity概览
  • 原文地址:https://www.cnblogs.com/HYDcn666/p/the_introduction_to_link_cut_tree.html
Copyright © 2020-2023  润新知