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;
    }
    
    
  • 相关阅读:
    SCILAB简介[z]
    UG OPEN API编程基础 2约定及编程初步
    Office 2003与Office 2010不能共存的解决方案
    UG OPEN API 编程基础 3用户界面接口
    NewtonRaphson method
    UG OPEN API编程基础 13MenuScript应用
    UG OPEN API编程基础 14API、UIStyler及MenuScript联合开发
    UG OPEN API编程基础 4部件文件的相关操作
    UG OPEN API编程基础 1概述
    16 UG Open的MFC应用
  • 原文地址:https://www.cnblogs.com/chy-2003/p/10570224.html
Copyright © 2011-2022 走看看