「学习笔记」Link Cut Tree
定义
起源是有些树要动态加边或者删边,所以这时候用到了
我们把 放到外层作为外层树,每个点被包含在一个单独的 中
其实这里主要是运用了 可以区间反转的功能(当然可以 但是复杂度多一个 )
同时在 里面中序遍历得到的点的深度是递增的,也就是说不一定在当前 的根就是这个子树里面深度最浅的点
因此我们把边就得拆成实边和虚边,这里在同一个 里面的边都是实边,而不在里面的边则是虚边
这里注意:父子连边不变,不是说当前 的根连向下个 的根来表示连边
那么就有一些定义
在 里面的父亲,不是原树
在 里面的左右儿子,真树里面的连边都是靠 来的
操作
access
联通根到当前点的链
这个每个 跟着做就行了,每次把当前点干到 的根,然后改儿子
这里是把 ,根据深度的原则不难得到
所以简单的代码如下:
inline void access(int x){
for(reg int y=0;x;x=fa[y=x]) splay(x),rs[x]=y,push_up(x);
return ;
}
其实这里是换了个更的方式:把更新儿子的步骤放到了上面,这里被覆盖的儿子的父亲没变,但是父子关系变成了虚边
makeroot
指定根
很好说,直接打通链时候翻上去就行了,但是很坑的是这样的话没有深度保证
所以要翻转整个当前 ,打标记即可,和普通 没有区别
inline void pushroot(int x){
swap(ls[x],rs[x]); fl[x]^=1;
return ;
}
inline void makeroot(int x){
access(x); splay(x); pushroot(x);
return ;
}
findroot
找到原树上的根
换到根之就找左儿子,也就是:
inline int findroot(int x){
access(x); splay(x);
while(ls[x]) x=ls[x];
return splay(x),x;//多splay来保证复杂度……
}
当然这样写是非常慢的,那么特定场景下可以用并查集来进行替换和卡常
split
打通一条链,随便钦定一个点为根然后连上就行了
inline void split(int x,int y){
makeroot(x); access(y); splay(y);
return ;
}
然后直接查询 就是这个路径上面的信息,需要理解一下为什么这样就能维护出来单独的路径
link
连接两个点之间的边
inline void link(int x,int y){
make_root(x); if(findroot(y)!=x) fa[x]=y;
return ;
}
cut
断开边,先把一个点转到必然是另一个的父亲然后判断合法性
也就是说:
inline void cut(int x,int y){
make_root(x);
if(findroot(y)!=x||fa[y]!=x||ls[y]) return ;
//第一个是不在一个树上,findroot(y) 之后 x 是这个splay的根
//如果y的父亲不是x那么必然没有连边,考虑findroot中的更改
//如果有ls[y] 那么就有越级父亲
rs[x]=0; fa[y]=0; push_up(x);
return ;
}
这里写 的 和一般的不太一样,具体如下:
维护一个是不是当前splay的根的函数:
inline bool isroot(int x){return ls[fa[x]]!=x&&rs[fa[x]]!=x;}
和 的时候记得判断 的情况,不能找到另一个splay上面
下方 标记的时候要注意从上往下,原因可以手玩一下
这样的话板子就随便打了
功能
维护链上信息
其实本质上就是 一下,然后有各种打标记的方式
Luogu 1501
裸题,直接打标记就行了
Luogu 4332
显然改变一个 的点的本质会修改一条链上的答案
肯定是临界的会改,问题转化成了维护链上最深的 不是 的点的位置
打通一条链的操作就是 ,然后在平衡树上二分
具体而言就是 维护几个子树面是不是都是 即可
时间复杂度
貌似有一个少 的写法,就是记录子树里面的最深 的点
如果修改值的话需要交换
这题写的原则就是多
维护双联通分量/联通性
Luogu2542
逆序之后考虑如何维护必经边
这个必经边容易让人想到点双,那么考虑缩点
每次如果 失败了就删掉环上的点,缩成一个新点,所有连的点都指向这个点
但是需要更改的是 的时候是要跳 的
维护生成树或者树边信息
边权很难维护,如果按照一些树题放到儿子上的话一变父子关系就废掉了
所以考虑拆点,把边权放到一个新的点上,因为比较优秀的编号方式,每个新点的
所以更改或者一些其他操作就好说了
link(e[i].id,e[i].x); link(e[i].id,e[i].y);
cur(e[i].id,e[i].x); cut(e[i].id,e[i].y);
NOI2014 魔法森林
看到是多维的先想降维,所以 掉
那么然后的问题是如何维护一个用 最小的生成树
那么每次加边如果成环就断掉环上最大的 然后更新答案
为啥原来那么菜还要去抄题解呀
维护虚子树信息
记 表示虚子树信息,那么不一样的地方首先是
for(reg int y=x;x;x=fa[y=x]){
splay(x);
s[x]-=s[rs[x]],si[x]+=rs[x];
s[x]+=s[y],si[x]-=s[y];
rs[x]=y;
}
然后 的时候要先把 整到根上面,防止祖先的信息被记漏
inline void link(int x,int y){
make_root(x); if(findroot(y)==x) return ;
fa[x]=y; access(y); splay(y); si[y]+=s[x];
push_up(y); //真的别忘记了多push_up
return ;
}
BJOI2014 大融合
貌似线段树合并随便做?
如果 的话考虑如果 之后那么有剩下的边就是虚的了
那么维护上 查询的时候回答
维护树上染色联通块
SP16549
新建一个根作为 的父亲,对于修改颜色操作,就把原来父亲节点的边断开,连上新的
需要用 维护虚子树的大小
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律