LinkCutTree学习笔记
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 的
root的fa连向原树中该实链链顶的父亲(特例), 而父亲没有这个儿子(显然)
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 点
- 发现将
root和fa[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学习笔记的更多相关文章
- LCT[Link-Cut-Tree学习笔记]
部分摘抄于 FlashHu candy99 所以文章篇幅较长 请有足够的耐心(不是 其实不用学好splay再学LCT的-/kk (至少现在我平衡树靠fhq) 如果学splay的话- 也许我菜吧-LCT ...
- [学习笔记]平衡树(Splay)——旋转的灵魂舞蹈家
1.简介 首先要知道什么是二叉查找树. 这是一棵二叉树,每个节点最多有一个左儿子,一个右儿子. 它能支持查找功能. 具体来说,每个儿子有一个权值,保证一个节点的左儿子权值小于这个节点,右儿子权值大于这 ...
- js学习笔记:webpack基础入门(一)
之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...
- PHP-自定义模板-学习笔记
1. 开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2. 整体架构图 ...
- PHP-会员登录与注册例子解析-学习笔记
1.开始 最近开始学习李炎恢老师的<PHP第二季度视频>中的“章节5:使用OOP注册会员”,做一个学习笔记,通过绘制基本页面流程和UML类图,来对加深理解. 2.基本页面流程 3.通过UM ...
- 2014年暑假c#学习笔记目录
2014年暑假c#学习笔记 一.C#编程基础 1. c#编程基础之枚举 2. c#编程基础之函数可变参数 3. c#编程基础之字符串基础 4. c#编程基础之字符串函数 5.c#编程基础之ref.ou ...
- JAVA GUI编程学习笔记目录
2014年暑假JAVA GUI编程学习笔记目录 1.JAVA之GUI编程概述 2.JAVA之GUI编程布局 3.JAVA之GUI编程Frame窗口 4.JAVA之GUI编程事件监听机制 5.JAVA之 ...
- seaJs学习笔记2 – seaJs组建库的使用
原文地址:seaJs学习笔记2 – seaJs组建库的使用 我觉得学习新东西并不是会使用它就够了的,会使用仅仅代表你看懂了,理解了,二不代表你深入了,彻悟了它的精髓. 所以不断的学习将是源源不断. 最 ...
- CSS学习笔记
CSS学习笔记 2016年12月15日整理 CSS基础 Chapter1 在console输入escape("宋体") ENTER 就会出现unicode编码 显示"%u ...
随机推荐
- Java并发编程:Java实现多线程的几种方式
在Java中,多线程主要的实现方式有四种:继承Thread类.实现Runnable接口.实现Callable接口通过FutureTask包装器来创建Thread线程.使用ExecutorService ...
- HDU2899Strange fuction(二分/三分)
传送门 题目大意:求 F(x) = 6 * x^7+8*x^6+7*x^3+5*x^2-y*x (0 <= x <=100):的最小值 题解:求个导,二分导函数零点,就是原函数最小值所在的 ...
- Paper | PyTorch: An Imperative Style, High-Performance Deep Learning Library
目录 0. 摘要 1. 简介 2. 背景 3. 设计原则 4. 针对易用性的核心设计 4.1 让深度学习模块不过是Python程序 4.2 互用性和可拓展性 4.3 自动差分 5. 针对高性能的PyT ...
- 推荐书单(网课)-人生/编程/Python/机器学习-130本
目录 总计(130本) 一.在读 二.将读 三.已读 非专业书单(77本) 四.已读 专业书单(53本) 五.已看网课(8个) 六.在看网课 一个人如果抱着义务的意识去读书,便不了解读书的艺术.--林 ...
- Java类加载机制以及双亲委派模型
一.Java类加载机制 1.概述 Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数,属性和方法等,Java允 ...
- React: React的属性验证机制
一.简介 在开发中,属性变量类型的验证,几乎是任何语言都必须关注的问题,因为如果传入的数据类型不对,轻者程序运行仅仅是给出警告⚠️,严重的会直接导致程序中断,APP闪退或者web页面挂掉,这是很严重的 ...
- pytorch_08_RNN
1.循环神经网络的提出是基于记忆模型的想法,期望网络能够记住前面出现的特征,并依据特征推断后面的结果,而且整体的网络结构不断循环,因而得名循环神经网络. 2.循环神经网络的基本结构特别简单,就是将网络 ...
- 搞定Junit单元测试{非专业}
1:测试分类 2:常用测试方法 2.1 断言语句 3: 基本测试 4: 组合测试 5:参数化测试 6:分类测试(Category) 1:测试分类 1. 黑盒测试:不需要写代码,给输入值,看程序是否能 ...
- PELT算法
参考:http://www.wowotech.net/process_management/PELT.html 本文是对https://lwn.net/Articles/531853/的翻译 mark ...
- Docker - 卷组管理(三)
一.不指定宿主机目录 首先运行一个nginx容器 docker run -d --name mynginx -p 8080:80 -v /usr/share/nginx/html nginx --na ...