LinkCutTree 学习笔记


参考来源

https://www.zybuluo.com/xzyxzy/note/1027479

https://www.cnblogs.com/zhoushuyu/p/8137553.html


目的&作用

树的动态加边/删边

维护两点联通性, 树链信息, 生成树等

概述

应为无根树可以随意钦点树根, Splay 树也同样

所以下面的算法都基于从根连出去的树链形成的 Splay 树, 以及换根, 等等操作

然后实际上并不用建出原树, 所有的连边的关系都在 LCT 中

详解

Splay以什么为关键值?以什么为下标?

以深度为关键值(不用具体存储, 用二叉查找树表示相对关系)

以原树中编号为下标

实边和虚边

  • 每一条由实边形成的树链由一个 Splay 维护
  • 每个点至多有一条连向其儿子的实边
  • 一个原树会被划分为多块 Splay

父与子

假设 ch[x][0/1] 代表在 Splay 中 x 节点的左右儿子, fa[x] 为父亲

  • 在原树中与 x 虚边连接的节点就不会出现在 Splay 树的父子中
  • 一个 Splay 的 rootfa 连向原树中该实链链顶的父亲(特例), 而父亲没有这个儿子(显然)

isroot(x)

判断 x 是否是它所在的 Splay 的根

判据很简单, 就是 "Splay 上父亲的左右儿子都不是它", 刚才讲过了

注意是左右儿子都不是, 因为以深度为下标, 而深度时常会改变

bool isroot(int x)
{
return ch[fa[x]][0] != x && ch[fa[x]][1] != x;
}

这样就不记录 root

正是因为 root 会连接两个不同的 Splay , 所以在\(LCT\)中要特别注意边界问题, 在rotate()splay()等中都会涉及

access(x)

将树根到 x 的路径串在一个 Splay 中, 并修改相关实边虚边

由于 Splay 的根是连向其他 Splay 的出口, 而实链链顶的父亲(\(fa[root]\))则是另一个 Splay 的入口, 我们主要关注这种节点, 还有 x

  • 发现将 rootfa[root] 改实边后, \(fa[root]\)的实边就要断开, 而该实儿子是\(ch[][1]\)(深度较大)
  • 切换虚实边只要 ch[fa[root]][1]=root 即可, 一步到位

我们能保证 root 刚被包含在根到 x 的路径上, 即新的 Splay 分区中(见makeroot), (但是为了保证复杂度?), 一开始将 x splay到根, 之后则选择\(fa[root]\), 即 fa[x] splay到根

还有, splay改变了树的形态, 而splay的路径上的点可以在rotate的时候pushup掉, 那么还剩下根刚好没有pushup, 单独做一下即可(pushup(fa[x])时会把 x 统计上去, 不用担心新实边上的上传)

总结一下代码就是这样

注意一开始 x 的右儿子会连接 0 , 就是把 x 和比 x 深的那一部分断开了, 分了一个 Splay 出去, 但并不影响

然后做完之后 x 在最底下

void access(int x)
{
for (int y = 0; x; y = x, x = fa[x])
splay(x), ch[x][1] = y, pushup(x);
}

"access"好难打啊,不如直接留一个头尾

基于access()的一些操作

makeroot()

原树换根,用处看下面一些函数就知道了

access(x)

发现现在已经把\(root --> x\)抓起来了,而 x 在最深处

于是splay(x) + reverse(x)就可以了(想想为什么?)

因为只需要这条链翻转, 而链上挂出去的小树不用

void makeroot(int x)
{
access(x); splay(x);
reverse(x);
}

findroot()

找 splay 上的根

看代码

int findroot(int x)
{
access(x); splay(x);
while (ch[x][0]) x = ch[x][0];
splay(x);
return x;
}

link(x, y)

void link(int x, int y)
{
if (findroot(x) == findroot(y)) return ;
makeroot(x);
fa[x] = y; //x整棵树接到y下面
}

split(x, y)cut(x, y)

可以随便换根的

看代码,想想为什么?

void split(int x, int y)
{
makeroot(x);
access(y); splay(y);
}
/*判断
void cut(int x, int y)
{
makeroot(x);
if (findroot(y) != x || fa[y] != x) return ; // 想想为什么?
split(x, y);
fa[x] = ch[y][0] = 0;
pushup(y);
}
*/
void cut(int x, int y)
{
split(x, y);
fa[x] = ch[y][0] = 0;
pushup(y);
}

reverse(x)

void reverse(int now)
{
if (!now) return ;
swap(ch[now][0], ch[now][1]);
rev[now] ^= 1;
}

rotate()splay(x)

splay的路径上从上到下先pushdown()

void rotate(int x)
{
int y = fa[x], z = fa[y];
int yx = (ch[y][1] == x), zy = (ch[z][1] == y);
if (!isroot(y)) ch[z][zy] = x;
fa[x] = z;
fa[ch[x][yx ^ 1]] = y; ch[y][yx] = ch[x][yx ^ 1];
fa[y] = x; ch[x][yx ^ 1] = y;
pushup(y); pushup(x);
}
void splay(int x)
{
if (!x) return ;
stk[top = 1] = x;
for (int p = x; !isroot(p); p = fa[p]) stk[++ top] = fa[p];
while (top) pushdown(stk[top --]);
while (!isroot(x))
{
int y = fa[x], z = fa[y];
int zy = (ch[z][1] == y), yx = (ch[y][1] == x);
if (!isroot(y))
(zy == yx) ? rotate(y) : rotate(x);
rotate(x);
}
}

后续见例题,基础代码见模板题

  • 带修改判断两点联通性
  • 带修改查询路径异或和

例题

BZOJ2049: Sdoi2008Cave 洞穴勘测

树, 三种操作, 连边, 删边(保证存在), 查询连通性

模板Link Cut Tree(动态树)

树, 四种操作, 查询路径上异或和, 连边(要求已联通则不连), 删边(不保证联通), 修改点权

修改点权:把根换成他,这样单点修改他就行了,影响之后要用的时候自然会算到

查询:split出来,完事

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm> using namespace std;
typedef long long LL;
const int MAXN = 3e5 + 10; inline LL in()
{
LL x = 0, flag = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') flag = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
return x * flag;
} int n, m; int fa[MAXN], ch[MAXN][2], rev[MAXN];
LL val[MAXN], sum[MAXN];
int top, stk[MAXN]; void reverse(int x)
{
if (!x) return;
swap(ch[x][0], ch[x][1]);
rev[x] ^= 1;
}
void pushup(int x)
{
sum[x] = sum[ch[x][0]] ^ sum[ch[x][1]] ^ val[x];
}
void pushdown(int x)
{
if (!rev[x]) return;
reverse(ch[x][0]); reverse(ch[x][1]);
rev[x] = 0;
}
bool isroot(int x) { return ch[fa[x]][0] != x && ch[fa[x]][1] != x; }
void rotate(int x)
{
int y = fa[x], z = fa[y];
int zy = (y == ch[z][1]), yx = (x == ch[y][1]);
if (!isroot(y)) ch[z][zy] = x;
fa[x] = z; // fa
ch[y][yx] = ch[x][yx ^ 1], fa[ch[x][yx ^ 1]] = y;
ch[x][yx ^ 1] = y; fa[y] = x;
pushup(y); pushup(x);
}
void splay(int x) // x --> root
{
if (!x) return ;
stk[top = 1] = x;
for (int tmp = x; !isroot(tmp); tmp = fa[tmp]) stk[++ top] = fa[tmp];
while (top) pushdown(stk[top --]);
while (!isroot(x))
{
int y = fa[x], z = fa[y];
int zy = (y == ch[z][1]), yx = (x == ch[y][1]);
if (!isroot(y))
(zy == yx) ? rotate(y) : rotate(x);
rotate(x);
}
pushup(x);
} void access(int x)
{
for (int y = 0; x; y = x, x = fa[x])
splay(x), ch[x][1] = y, pushup(x);
}
void makeroot(int x)
{
access(x); splay(x);
reverse(x);
}
int findroot(int x)
{
access(x); splay(x);
while (ch[x][0]) x = ch[x][0];
splay(x);
return x;
}
void link(int x, int y)
{
if (findroot(x) == findroot(y)) return ;
makeroot(x);
fa[x] = y;
}
void split(int x, int y)
{
makeroot(x);
access(y); splay(y);
}
void cut(int x, int y)
{
makeroot(x);
if (findroot(y) != x || fa[y] != x) return ;
split(x, y);
fa[x] = ch[y][0] = 0;
pushup(y);
}
LL query(int x, int y)
{
split(x, y);
return sum[y];
}
void modify(int x, LL y)
{
access(x); splay(x);
val[x] = y;
pushup(x);
} int main()
{
n = in(), m = in();
for (int i = 1; i <= n; ++ i) val[i] = in();
while (m --)
{
int opt = in(), x = in(); LL y = in();
if (opt == 0) printf("%lld\n", query(x, y));
else if (opt == 1) link(x, y);
else if (opt == 2) cut(x, y);
else if (opt == 3) modify(x, y);
}
return 0;
}

WC2006水管局长

https://www.lydsy.com/JudgeOnline/problem.php?id=2594

https://www.luogu.org/problemnew/show/P4172

BZOJ3626: LNOI2014LCA

BZOJ2002: [Hnoi2010]Bounce 弹飞绵羊

裸题, 根据题意建立虚拟点, 删边加边,

询问是原树中以某个点为根的某个点的深度

可以先 makeroot 根, 然后 access, splay x , 直接用 siz 求得,

也可你当成求路径长度

再放一个板子


namespace LCT
{
int ch[MAXN][2], fa[MAXN];
int siz[MAXN];
bool rev[MAXN];
int stk[MAXN], top;
void init(int x)
{
siz[x] = 1;
ch[x][0] = ch[x][1] = fa[x] = 0;
}
bool isroot(int x)
{
return ch[fa[x]][1] != x && ch[fa[x]][0] != x;
}
void pushup(int now)
{
siz[now] = siz[ch[now][0]] + siz[ch[now][1]] + 1;
}
void reverse(int now)
{
if (!now) return ;
swap(ch[now][0], ch[now][1]);
rev[now] ^= 1;
}
void pushdown(int now)
{
if (!rev[now]) return ;
reverse(ch[now][0]); reverse(ch[now][1]);
rev[now] = 0;
}
void rotate(int x)
{
int y = fa[x], z = fa[y];
int yx = (ch[y][1] == x), zy = (ch[z][1] == y);
if (!isroot(y)) ch[z][zy] = x;
fa[x] = z;
fa[ch[x][yx ^ 1]] = y; ch[y][yx] = ch[x][yx ^ 1];
fa[y] = x; ch[x][yx ^ 1] = y;
pushup(y); pushup(x);
}
void splay(int x)
{
if (!x) return ;
stk[top = 1] = x;
for (int p = x; !isroot(p); p = fa[p]) stk[++ top] = fa[p];
while (top) pushdown(stk[top --]);
while (!isroot(x))
{
int y = fa[x], z = fa[y];
int zy = (ch[z][1] == y), yx = (ch[y][1] == x);
if (!isroot(y))
(zy == yx) ? rotate(y) : rotate(x);
rotate(x);
}
}
void access(int x)
{
for (int pre = 0; x != 0; pre = x, x = fa[x])
splay(x), ch[x][1] = pre, pushup(x);
}
void makeroot(int x)
{
access(x); splay(x); reverse(x);
}
int findroot(int x)
{
access(x); splay(x);
while (ch[x][0]) x = ch[x][0];
splay(x);
return x;
}
void link(int x, int y)
{
if (findroot(x) == findroot(y)) return ;
makeroot(x);
fa[x] = y;
}
void split(int x, int y)
{
makeroot(x);
access(y); splay(y);
}
void cut(int x, int y)
{
split(x, y);
fa[x] = ch[y][0] = 0;
pushup(y);
}
int getdep(int x)
{
makeroot(n + 1);
access(x);
splay(x);
return siz[ch[x][0]] + 1;
}
} int main()
{
n = in();
for (int i = 1; i <= n + 1; ++ i)
{
LCT::init(i);
}
for (int i = 1; i <= n; ++ i)
{
a[i] = in();
LCT::link(i, min(i + a[i], n + 1));
}
m = in();
while (m --)
{
int opt = in();
if (opt == 1)
{
int x = in() + 1;
printf("%d\n", LCT::getdep(x) - 1);
}
else
{
int x = in() + 1, v = in();
// (x --> x + a[x]) ==> (x --> x + v)
LCT::cut(x, min(x + a[x], n + 1));
LCT::link(x, min(x + v, n + 1)); a[x] = v;
}
}
return 0;
}

LinkCutTree学习笔记的更多相关文章

  1. LCT[Link-Cut-Tree学习笔记]

    部分摘抄于 FlashHu candy99 所以文章篇幅较长 请有足够的耐心(不是 其实不用学好splay再学LCT的-/kk (至少现在我平衡树靠fhq) 如果学splay的话- 也许我菜吧-LCT ...

  2. [学习笔记]平衡树(Splay)——旋转的灵魂舞蹈家

    1.简介 首先要知道什么是二叉查找树. 这是一棵二叉树,每个节点最多有一个左儿子,一个右儿子. 它能支持查找功能. 具体来说,每个儿子有一个权值,保证一个节点的左儿子权值小于这个节点,右儿子权值大于这 ...

  3. js学习笔记:webpack基础入门(一)

    之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...

  4. PHP-自定义模板-学习笔记

    1.  开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2.  整体架构图 ...

  5. PHP-会员登录与注册例子解析-学习笔记

    1.开始 最近开始学习李炎恢老师的<PHP第二季度视频>中的“章节5:使用OOP注册会员”,做一个学习笔记,通过绘制基本页面流程和UML类图,来对加深理解. 2.基本页面流程 3.通过UM ...

  6. 2014年暑假c#学习笔记目录

    2014年暑假c#学习笔记 一.C#编程基础 1. c#编程基础之枚举 2. c#编程基础之函数可变参数 3. c#编程基础之字符串基础 4. c#编程基础之字符串函数 5.c#编程基础之ref.ou ...

  7. JAVA GUI编程学习笔记目录

    2014年暑假JAVA GUI编程学习笔记目录 1.JAVA之GUI编程概述 2.JAVA之GUI编程布局 3.JAVA之GUI编程Frame窗口 4.JAVA之GUI编程事件监听机制 5.JAVA之 ...

  8. seaJs学习笔记2 – seaJs组建库的使用

    原文地址:seaJs学习笔记2 – seaJs组建库的使用 我觉得学习新东西并不是会使用它就够了的,会使用仅仅代表你看懂了,理解了,二不代表你深入了,彻悟了它的精髓. 所以不断的学习将是源源不断. 最 ...

  9. CSS学习笔记

    CSS学习笔记 2016年12月15日整理 CSS基础 Chapter1 在console输入escape("宋体") ENTER 就会出现unicode编码 显示"%u ...

随机推荐

  1. 《专访 RocketMQ 联合创始人:项目思路、技术细节和未来规划》

    专访 RocketMQ 联合创始人:项目思路.技术细节和未来规划   木环 阅读数:138092017 年 2 月 20 日 18:00   编者按 这些年开源氛围越来越好,各大 IT 公司都纷纷将一 ...

  2. Java四个关键字 this super final static

    一.this ​ 关键字主要有三个应用: this调用本类中的属性,也就是类中的成员变量: this调用本类中的其他方法: this调用本类中的其他构造方法初始化对象,调用时要放在构造方法的首行. 引 ...

  3. CF1248E Queue in the Train

    题目链接 problem 火车上的一列人要去排队接水.每个人都会在某个特定的时刻口渴.口渴之后他要去排队接水,如果他前面的座位有人已经在排队或者正在接水,那么他就不会去排队.否则他就会去排队.每个人接 ...

  4. 【洛谷5465】[PKUSC2018] 星际穿越(倍增)

    点此看题面 大致题意: 给定\(l_{2\sim n}\),其中\(l_i\)表示\([l_i,i-1]\)的所有点与\(i\)之间存在一条长度为\(1\)的双向路径.每次询问给出\(l,r,x\), ...

  5. Azure Sphere Development Environment Setup

    1. Visual Studio 目前,Visual Studio 2017/2019支持Azure Sphere开发,后续,微软会加入Visual Studio Code的支持.以Visual St ...

  6. python做中学(二)bool()函数的用法

    定义: bool() 函数用于将给定参数转换为布尔类型,如果没有参数,返回 False. bool 是 int 的子类. 语法: 以下是 bool() 方法的语法: class bool([x] 参数 ...

  7. Feign原理 (图解)

    疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 疯狂创客圈 正在进行分布式和高并发基础原理的研习,进行已经发布一些基础性的文章: 一.版本1 :springcloud ...

  8. 123: The filename, directory name, or volume label syntax is incorrect今天玩nginx的时候报错

    今天在win下玩nginx的时候 提示500错误 看了下nginx的logs  提示 123: The filename, directory name, or volume label syntax ...

  9. c# winform 窗体失去焦点关闭(钩子实现)

    先来一个辅助类 using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Inte ...

  10. 读Xamarin文档记录

    //怎样判断Wifi是否连接if (Connectivity.NetworkAccess == NetworkAccess.None) { ... } 连接改变的事件,判断事件改变后是否还处于连接状态 ...