zoukankan      html  css  js  c++  java
  • 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;
    } 
    
  • 相关阅读:
    Hadoop出现 Wrong FS: hdfs://......错误的解决方法
    在Linux下安装JDK环境
    卸载Linux自带的JDK
    hadoop1.2.1伪分布模式安装教程
    spring配置bean的生命周期
    spring注入的四种方式
    python re模块search()与match()区别
    VB.NET操作Excel
    位运算
    Python简单源码解析
  • 原文地址:https://www.cnblogs.com/yzhang-rp-inf/p/10201857.html
Copyright © 2011-2022 走看看