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 ...
随机推荐
- Redis思维导图
Redis基本数据结构 1.String 1.1 数据结构 long len byte数组长度 long free 可用数组长度 char buff[] 数据内容 1.2 命令 键值:设置值通过字符串 ...
- __setattr__和__delattr__和__getattr__
目录 一.__setattr__ 二.__delattr__ 三. __getattr__ class Foo: x = 1 def __init__(self, y): self.y = y def ...
- angularjs 一篇文章看懂自定义指令directive
壹 ❀ 引 在angularjs开发中,指令的使用是无处无在的,我们习惯使用指令来拓展HTML:那么如何理解指令呢,你可以把它理解成在DOM元素上运行的函数,它可以帮助我们拓展DOM元素的功能.比如 ...
- SVN基本使用
1.把服务器的所有内容下载到本地 svn checkout 服务器地址 --username=使用者 --password=密码 2.添加文件 touch main.m(文件名) : 创建main.m ...
- 《细说PHP》第四版 样章 第23章 自定义PHP接口规范 2
23.1.3 接口的应用和优势 API是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无须访问源码,或理解内部工作机制的细节.接口应用的一些常见场景如下 ...
- 企业如何做好B2C电商平台
导语本文主要讲了两个方面:1.企业如何定位B2C电商平台:2.企业做B2C遇到的问题. 一.企业如何定位B2C电商平台传统企业做B2C电子商务一般选用品牌.渠道.平台模式这三种,品牌模式是在网上建立一 ...
- 3、Hibernate的多表关联
一.数据库中的表关系: 一对一关系 一个人对应一张身份证,一张身份证对应一个人,一对一关系是最好理解的一种关系,在数据库建表的时候可以将人表的主键放置与身份证表里面,也可以将身份证表的主键放置于人表里 ...
- Redis for OPS 06:Redis Cluster 集群
写在前面的话 前面的主从,HA 都只是解决我们数据安全性方面的问题,并没有解决我们业务瓶颈的问题.当业务并发到达一定瓶颈的时候,我们需要对服务进行横向扩展,而不是纵向扩展.这就需要引入另外一个东西,R ...
- 记一次hosts配置内容过多引起的故障
问题环境 系统环境 CentOS release 6.9 (Final) 2.6.32-696.16.1.el6.x86_64 Java 环境 1.8.0_151 Apollo 1.4.0 代码框架 ...
- Python中使用requests和parsel爬取喜马拉雅电台音频
场景 喜马拉雅电台: https://www.ximalaya.com/ 找到一步小说音频,这里以下面为例 https://www.ximalaya.com/youshengshu/16411402/ ...