zoukankan      html  css  js  c++  java
  • Link-Cut Tree(LCT) 教程

    前置知识

    请先对树链剖分和Splay有所了解。LCT基于树链剖分,而本文的数据结构采用Splay。

    Splay 戳这里,树链剖分戳这里

    介绍

    注意:请务必分清原树和我们操作的树。

    以下图片参考Yang Zhe的论文

    假设原来有这么一棵树:

    1

    我们把它剖分成这样子:

    2

    红色表示重边,黑色表示轻边。

    我们考虑用Splay维护每条重链上的信息。我们规定Splay的中序遍历结果是按点的深度从小到大的。所以同一深度的两个点不可能在同一个Splay中。然后我们发现,轻边连接的深度较大的点一定为一个Splay中深度最浅的点。于是,我们让一个Splay的根的Father表示这颗Splay中深度最小的点在原树中的父亲,其余的Father表示它们在Splay中的父亲。然后我们可以考虑一下操作:

    Access

    Access(x)的作用是将x到根的路径变成一条重链。对于例子里的Access(N)之后就是这样:

    3

    至于为什么Access(N)要把N-O变成轻边,这是为了其他操作方便考虑,我们令Access(x)之后,x是它所在的重链中深度最深的点。

    所以Access(x)的过程大概可以这样子:

    1、X表示当前要往上连的节点,Last表示要连过来的节点。Last一开始为0。

    2、将X旋转到它所在的Splay的根,断开右子树,这样X就是Splay中在原树中深度最大的点了。

    3、连接X和Last。

    4、如果X不为原树的根,Last=X, X = Father[ X ],跳到第二步。

    大家可一画个图感受一下QwQ。

    所以Access可以这样写:

    void Access( int x ) {
        for( int Last = 0; x; Last = x, x = Father[ x ] ) {
            Splay( x );//将x旋转到当前Splay的根。
            Child[ x ][ 1 ] = Last;//断开右儿子并且连接Last和x。
            Collect( x );//重新获取信息
        }
        return;
    }
    

    FindRoot

    FindRoot(x)返回的是x在原树中的根。我们联通x与根节点后,找Splay中最左边的就可以了。

    int FindRoot( int x ) {
        Access( x );
        Splay( x );
        TagDown( x );//为了正确性,不要忘记下传标记。
        while( Child[ x ][ 0 ] ) {
            x = Child[ x ][ 0 ];
            TagDown( x );
        }
        Splay( x );//保证Cut正确性
        return x;
    }
    

    MakeRoot

    MakeRoot(x)表示把x置为原树的根。我们首先Access(x),这样就有一条从根到x的路径了。然后可以Splay(x)后翻转这颗Splay,这样x就变成原树的根了(深度最小)。

    所以MakeRoot可以这样写:

    void MakeRoot( int x ) {
        Access( x );
        Splay( x );
        Tag[ x ] ^= 1;//标记翻转
        return;
    }
    

    Split

    Split(x,y)提取出原树中从x到y的一条链。由于Splay中不允许存在深度相同的点。所以我们这样操作:

    首先MakeRoot(x),使x变成原树的根,然后Access(y)即可。为了操作方便,最后Splay(y)。

    void Split( int x, int y ) {
        MakeRoot( x );
        Access( y );
        Splay( y );
        return;
    }
    

    Link(x,y)表示连接点x和y。

    我们先把x变成根,如果y个根是x,那么就不需要连了,否则令Father[ y ] = x。

    由于FindRoot(y)中,y已经变成了Splay中的根,所以这样操作是可以的。

    void Link( int x, int y ) {
        MakeRoot( x );
        if( FindRoot( y ) == x ) return;
        Father[ x ] = y;
        return;
    }
    

    Cut

    Cut(x, y)表示断开边x-y。

    同样的,我们先MakeRoot(x)。然后我们要看边x-y是否存在。首先要满足FindRoot(y)=x。同样的,FindRoot后,y变成了Splay的根。然后我们要判断Father[ y ] = x。这样还不够,Child[ y ][ 0 ]也必须是空的才行,我们要保证y是直接与x相连的。

    所以Cut可以这样写:

    void Cut( int x, int y ) {
        MakeRoot( x );
        if( FindRoot( y ) != x || Father[ y ] != x || Child[ y ][ 0 ] != 0 ) return;//判断是否可以删。
        Father[ y ] = Child[ x ][ 1 ] = 0;//断开连接。
        Collect( x );//重新修改要维护的值。
        return;
    }
    

    到此,LCT的基本操作就结束了。

    关于Splay中操作的一点说明:

    涉及到与一般Splay操作不同的有几个点:

    1、所有Splay操作都是旋转到根,所以Splay操作只需传入一个参数。Splay前需要处理标记,注意从上往下操作。

    2、每一棵Splay的根节点的Father代表该Splay中原树中深度最小点的Father,而不是NULL,所以有如下问题需要注意:

    1、Rotate时候需要考虑父亲节点是否为当前节点的根。如果是,那么就不能更改祖父节点的儿子信息,因为祖父节点在另外一颗Splay中。

    2、Splay的时候判断是否为根,不是简单地判断父亲是否为空。因为根的父亲指向另一棵Splay。

    判断是否为当前Splay的根可以这样写:

    bool IsRoot( x ) {
        return Child[ Father[ x ] ][ 0 ] == x || Child[ Father[ x ] ][ 1 ] == x;
    }
    

    3、上述程序片段中涉及到Collect和TagDown,视具体题目而定。

    模板

    题目链接

    #include <bits/stdc++.h>
    using namespace std;
    
    const int Maxn = 300010;
    int Father[ Maxn ], Child[ Maxn ][ 2 ], Tag[ Maxn ], Sum[ Maxn ], Value[ Maxn ];
    int n, m;
    int Stack[ Maxn ], Num;
    
    void Read() {
    	scanf( "%d%d", &n, &m );
    	for( int i = 1; i <= n; ++i ) {
    		int x; scanf( "%d", &x );
    		Father[ i ] = 0;
    		Child[ i ][ 0 ] = Child[ i ][ 1 ] = 0;
    		Tag[ i ] = 0;
    		Sum[ i ] = Value[ i ] = x;
    	}
    	return;
    }
    
    void Collect( int Index ) {
    	Sum[ Index ] = Sum[ Child[ Index ][ 0 ] ] ^ Sum[ Child[ Index ][ 1 ] ] ^ Value[ Index ];
    	return;
    }
    
    void TagDown( int x ) {
    	if( Tag[ x ] ) {
    		Tag[ x ] = 0;
    		swap( Child[ x ][ 0 ], Child[ x ][ 1 ] );
    		Tag[ Child[ x ][ 0 ] ] ^= 1;
    		Tag[ Child[ x ][ 1 ] ] ^= 1;
    	}
    	return;
    }
    
    bool IsRoot( int x ) {
    	return !( ( Child[ Father[ x ] ][ 0 ] == x ) || ( Child[ Father[ x ] ][ 1 ] == x ) );
    }
    
    void Rotate( int C ) {
    	int B = Father[ C ];
    	int A = Father[ B ];
    	int Tag = Child[ B ][ 1 ] == C;
    	if( !IsRoot( B ) ) Child[ A ][ Child[ A ][ 1 ] == B ] = C;
    	Father[ C ] = A;
    	Father[ Child[ C ][ Tag ^ 1 ] ] = B;
    	Child[ B ][ Tag ] = Child[ C ][ Tag ^ 1 ];
    	Child[ C ][ Tag ^ 1 ] = B;
    	Father[ B ] = C;
    	Collect( B ); Collect( C );
    	return;
    }
    
    void Splay( int x ) {
    	Num = 0;
    	Stack[ ++Num ] = x;
    	int t = x;
    	for( ; !IsRoot( t ); t = Father[ t ] ) Stack[ ++Num ] = Father[ t ];
    	for( int i = Num; i >= 1; --i ) TagDown( Stack[ i ] );
    	while( !IsRoot( x ) ) {
    		int y = Father[ x ];
    		int z = Father[ y ];
    		if( !IsRoot( y ) ) 
    			if( ( Child[ z ][ 0 ] == y ) ^ ( Child[ y ][ 0 ] == z ) ) 
    				Rotate( x );
    			else 
    				Rotate( y );
    		Rotate( x );
    	}
    	Collect( x );
    	return;
    }
    
    void Access( int x ) {
    	for( int i = 0; x; i = x, x = Father[ x ] ) {
    		Splay( x );
    		Child[ x ][ 1 ] = i;
    		Collect( x );
    	}
    	return;
    }
    
    void MakeRoot( int x ) {
    	Access( x );
    	Splay( x );
    	Tag[ x ] ^= 1;
    	return;
    }
    
    int FindRoot( int x ) {
    	Access( x ); Splay( x );
    	TagDown( x );
    	while( Child[ x ][ 0 ] ) {
    		x = Child[ x ][ 0 ];
    		TagDown( x );
    	}
    	Splay( x );
    	return x;
    }
    
    void Split( int x, int y ) {
    	MakeRoot( x );
    	Access( y );
    	Splay( y );
    	return;
    }
    
    void Link( int x, int y ) {
    	MakeRoot( x );
    	if( FindRoot( y ) == x ) return;
    	Father[ x ] = y;
    	return;
    }
    
    void Cut( int x, int y ) {
    	MakeRoot( x );
    	if( FindRoot( y ) != x || Child[ y ][ 0 ] || Father[ y ] != x ) return;
    	Father[ y ] = Child[ x ][ 1 ] = 0;
    	Collect( x );
    	return;
    }
    
    int main() {
    	Read();
    	for( int i = 1; i <= m; ++i ) {
    		int Opt, x, y; scanf( "%d%d%d", &Opt, &x, &y );
    		if( Opt == 0 ) {
    			Split( x, y );
    			printf( "%d
    ", Sum[ y ] );
    		}
    		if( Opt == 1 ) Link( x, y );
    		if( Opt == 2 ) Cut( x, y );
    		if( Opt == 3 ) {
    			Splay( x );
    			Value[ x ] = y;
    		}
    	}
    	return 0;
    }
    
    
  • 相关阅读:
    最难的事
    性格决定命运,习惯决定未来
    系统构架师之路
    时间是经不起浪费的
    如何投资自己,增加自身价值!
    最好的程序员大多是自学成才的
    杂记
    Win7启动Oracle出错
    推荐代码生成器工具排行
    Hibernate 与 Oracle 11g 的问题
  • 原文地址:https://www.cnblogs.com/chy-2003/p/10570224.html
Copyright © 2011-2022 走看看