Link-Cut Tree(LCT) 教程
前置知识
请先对树链剖分和Splay有所了解。LCT基于树链剖分,而本文的数据结构采用Splay。
介绍
注意:请务必分清原树和我们操作的树。
以下图片参考Yang Zhe的论文
假设原来有这么一棵树:

我们把它剖分成这样子:

红色表示重边,黑色表示轻边。
我们考虑用Splay维护每条重链上的信息。我们规定Splay的中序遍历结果是按点的深度从小到大的。所以同一深度的两个点不可能在同一个Splay中。然后我们发现,轻边连接的深度较大的点一定为一个Splay中深度最浅的点。于是,我们让一个Splay的根的Father表示这颗Splay中深度最小的点在原树中的父亲,其余的Father表示它们在Splay中的父亲。然后我们可以考虑一下操作:
Access
Access(x)的作用是将x到根的路径变成一条重链。对于例子里的Access(N)之后就是这样:

至于为什么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
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\n", Sum[ y ] );
}
if( Opt == 1 ) Link( x, y );
if( Opt == 2 ) Cut( x, y );
if( Opt == 3 ) {
Splay( x );
Value[ x ] = y;
}
}
return 0;
}
Link-Cut Tree(LCT) 教程的更多相关文章
- 洛谷P3690 [模板] Link Cut Tree [LCT]
题目传送门 Link Cut Tree 题目背景 动态树 题目描述 给定n个点以及每个点的权值,要你处理接下来的m个操作.操作有4种.操作从0到3编号.点从1到n编号. 0:后接两个整数(x,y),代 ...
- BZOJ 3282 Link Cut Tree (LCT)
题目大意:维护一个森林,支持边的断,连,修改某个点的权值,求树链所有点点权的异或和 洛谷P3690传送门 搞了一个下午终于明白了LCT的原理 #include <cstdio> #incl ...
- Luogu 3690 Link Cut Tree
Luogu 3690 Link Cut Tree \(LCT\) 模板题.可以参考讲解和这份码风(个人认为)良好的代码. 注意用 \(set\) 来维护实际图中两点是否有直接连边,否则无脑 \(Lin ...
- LCT总结——概念篇+洛谷P3690[模板]Link Cut Tree(动态树)(LCT,Splay)
为了优化体验(其实是强迫症),蒟蒻把总结拆成了两篇,方便不同学习阶段的Dalao们切换. LCT总结--应用篇戳这里 概念.性质简述 首先介绍一下链剖分的概念(感谢laofu的讲课) 链剖分,是指一类 ...
- LuoguP3690 【模板】Link Cut Tree (动态树) LCT模板
P3690 [模板]Link Cut Tree (动态树) 题目背景 动态树 题目描述 给定n个点以及每个点的权值,要你处理接下来的m个操作.操作有4种.操作从0到3编号.点从1到n编号. 0:后接两 ...
- P3690 【模板】Link Cut Tree (动态树)
P3690 [模板]Link Cut Tree (动态树) 认父不认子的lct 注意:不 要 把 $fa[x]$和$nrt(x)$ 混 在 一 起 ! #include<cstdio> v ...
- Link Cut Tree学习笔记
从这里开始 动态树问题和Link Cut Tree 一些定义 access操作 换根操作 link和cut操作 时间复杂度证明 Link Cut Tree维护链上信息 Link Cut Tree维护子 ...
- Link Cut Tree 总结
Link-Cut-Tree Tags:数据结构 ##更好阅读体验:https://www.zybuluo.com/xzyxzy/note/1027479 一.概述 \(LCT\),动态树的一种,又可以 ...
- 【刷题】洛谷 P3690 【模板】Link Cut Tree (动态树)
题目背景 动态树 题目描述 给定n个点以及每个点的权值,要你处理接下来的m个操作.操作有4种.操作从0到3编号.点从1到n编号. 0:后接两个整数(x,y),代表询问从x到y的路径上的点的权值的xor ...
- LG3690 【模板】Link Cut Tree (动态树)
题意 给定n个点以及每个点的权值,要你处理接下来的m个操作.操作有4种.操作从0到3编号.点从1到n编号. 0:后接两个整数(x,y),代表询问从x到y的路径上的点的权值的xor和.保证x到y是联通的 ...
随机推荐
- Java代码 简单用于处理和数据库相关的操作
package util; import org.apache.commons.beanutils.BeanUtils; import java.lang.reflect.InvocationTarg ...
- 【GDOI】2018题目及题解(未写完)
我的游记:https://www.cnblogs.com/huangzihaoal/p/11154228.html DAY1 题目 T1 农场 [题目描述] [输入] 第一行,一个整数n. 第二行,n ...
- 从入门到自闭之Python高阶函数
高阶函数:内部帮忙做了一个for循环 filter:筛选过滤 语法: filter(function,iterable) function: 1.指定过滤规则(函数的内存地址) 2.用来筛选的函数,在 ...
- 编写 Bash 补全脚本
编写 Bash 补全脚本 对于Linuxer来说,自动补全是再熟悉不过的一个功能了.当你在命令行敲下部分的命令时,肯定会本能地按下Tab键补全完整的命令,当然除了命令补全之外,还有文件名补全. B ...
- 106、Label 控制 Service的位置 (Swarm13)
参考https://www.cnblogs.com/CloudMan6/p/8038799.html 上一节我们讨论了 Service部署的两种模式,global mode 和 replicate ...
- 第二十篇 jQuery 初步学习2
jQuery 初步学习2 前言: 老师这里啰嗦一下,因为考虑到一些同学,不太了解WEB前端这门语言.老师就简单的说一下,写前端,需要什么:一台笔记本.一个文本编辑器.就没啦!当然,写这门语言, ...
- dubbo学习笔记二(服务调用)
项目结构 代码示例 由于之前的IEchoService 的一个方法只是在服务端控制台打印,不便在浏览器测试,所以新添加的方法 api和服务端代码变更 public interface IEchoSer ...
- ZeroMQ 三种模式python3实现
ZeroMQ是一个消息队列网络库,实现网络常用技术封装.在C/S中实现了三种模式,这段时间用python简单实现了一下,感觉python虽然灵活.但是数据处理不如C++自由灵活. Request-Re ...
- Ubuntu安装配置Tensorflow-GPU
Ubuntu 16.04 + GTX 1080 Ti + CUDA 9.0 + Cudnn 7.1 安装配置 1. 安装显卡驱动 首先查看一下自己的电脑需要怎样的驱动,我们可以先到 http://ww ...
- 初识linux内核漏洞利用
0x00 简介 之前只接触过应用层的漏洞利用, 这次第一次接触到内核层次的,小结一下. 0x01 概况 这次接触到的,是吾爱破解挑战赛里的一个题,给了一个有问题的驱动程序,要求在ubuntu 14.0 ...