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. mysql 经典练习题上

    MY SQL 三张表 emp 表, 字段empno, ename, job, mgr, hiredate, sal, comm, deptno dept表 , 字段 deptno, dname, lo ...

  2. js正则高级函数(replace,matchAll用法),实现正则替换(实测很有效)

    有这么一个文档,这是在PC端显示的效果,如果放在移动端,会发现字体大小是非常大的,那么现在想让这个字体在移动端能按照某个比例缩小,后台返回的数据格式是: <html> <head&g ...

  3. ROS kinetic + Realsens D435i + ORK + LINEMOD 物体识别

    1. ORK 网址:https://wg-perception.github.io/object_recognition_core/ ORK (Object Recognition Kitchen) ...

  4. Java 异常面试问题与解答

    Java 提供了一种健壮且面向对象的方法来处理称为 Java异常处理的异常情况. 1. Java中的异常是什么? 异常是在程序执行期间可能发生的错误事件,它会破坏其正常流程.异常可能源于各种情况,例如 ...

  5. H5混合应用之上下文切换

    一.native/web/hybrid 简介 目前主流应用程序大体分为:Native App(原生应用).Web App(网页应用).Hybrid App(混合应用),它们三者的优缺点比较如下表: 应 ...

  6. postman请求数据库方法(Omysql)

    一.github 地址: https://github.com/liyinchigithub/Omysql 二.效果 三.使用方式 如果你电脑已经安装配置 Git.node 环境,可以直接按下面步骤进 ...

  7. SpringBoot开发案例之mail中文附件名字乱码

    最近在开发一个邮件发送多附件的微服务,使用的是org.springframework.mail.javamail.JavaMailSender;包下面的JavaMailSender 但是发送出来的附件 ...

  8. Java开发桌面程序学习(二)————fxml布局与控件学习

    JavaFx项目 新建完项目,我们的项目有三个文件 Main.java 程序入口类,载入界面并显示 Controller.java 事件处理,与fxml绑定 Sample.fxml 界面 sample ...

  9. C# MediaPlayer

    using System.Windows.Media; using Newtonsoft.Json; using System.ComponentModel; namespace ConsoleApp ...

  10. 如何搭建属于自己的Web服务器

    如今随着计算机和互联网技术的发展,上网现在已经不再是什么难事,打开浏览器,我们可以访问各种论坛站点,比如CSDN.博客园等,各种视频网站,例如爱奇艺,B站等.在网上我们可以写文章,看视频,购物,打游戏 ...