动态树 介绍篇
动态树 介绍篇#
简介#
,全名 ,一般基于 + 实链剖分 来实现
能够支持许多的操作:
-
查询/修改树上某条链的信息
-
将任意一点变为原树的根
-
动态连边/删边
-
动态维护连通性
-
求
这些操作均摊时间复杂度是 的
总而言之, 是一种非常实用的数据结构
就是貌似现在不咋考
基本思路及实现#
实链剖分#
实链剖分这个东西其实可以类比重链剖分,我们将某一个儿子的连边划分为实边,而连向其他子树的边划分为虚边
但是在 中这个虚边实边是会动态变化的,我们需要用灵活的 来维护
#
一棵 是由很多棵 构成的
- 维护了什么?
每一棵 都包含了一条由原树中从上到下深度严格递增的节点构成的链,且 中的节点是按深度排序的
也就是说,每一棵 的中序遍历得到的节点深度依次递增
而且,每个节点都必须在且仅在一棵 内
- 实边和虚边在 中是如何体现的?
实边连接了两个同一棵 的节点,而虚边则是连接了两个不同 的节点
由于实链剖分的性质,每个节点必须且仅能向一个儿子连一条实边,而与剩下的所有儿子均连虚边
为了维持树的形状, 采用了认父不认子的方法来维护,也就是虚边相连的两个点 中,只能有 的父亲为 (假设 深度更小), 没有 这个儿子
这样我们就能够维护每一棵 的完整性
核心操作#
这里先说明一下
名称 | 含义 |
---|---|
的左儿子 | |
的右儿子 | |
的父亲 | |
的权值 | |
中 所有子树的异或和 | |
中 是否要反转子树的懒标记 |
- 操作
:让 到根节点路径上所有链都变为实链
inline void access(int x){
for(int y=0;x;x=f[y=x]){
splay(x);
r(x)=y;//注意要维持splay深度有序
push_up(x);
}
//access的过程实际上是不断把x向上旋到当前splay的根,然后切断原来的实链,将其更新成当前实链
//这样仍能保证每个节点仅有一条实边,且x->root均为实边
//最开始会将ch[x][1]赋值成0,这样就能保证x->x的子节点没有实边
}
- 以及 操作
:下放 的子树翻转的懒标记
:翻转 的子树
inline void rev(int x){swap(l(x),r(x));lz[x]^=1;}
inline void push_down(int x){
if(!lz[x]) return;
if(l(x)) rev(l(x));
if(r(x)) rev(r(x));
lz[x]=0;
}
- 操作
:让 变为原树的根
inline void makeroot(int x){
access(x);//连x->root,此时在x->root的路径中x的深度最大
splay(x);//x变成root
rev(x);//把当前splay翻转,让x深度最小
//x深度最小了,那么x此时就是原树的根
}
- 操作
:找到 所在原树的树根
其主要用于判断两点的连通性
inline int findroot(int x){
access(x);splay(x);
for(;l(x);x=l(x)) push_down(x);
//找根的时候不能保证splay中到根的路径上的翻转标记全push_down了
splay(x);
return x;
}
- 操作
:判断 是否为某一棵 的根
inline bool rootful(int x){
return l(f[x])!=x&&r(f[x])!=x;
}//x和f[x]是否连轻边,即x是否为根
- 操作
:把 的路径拉出来变成一棵以 为根的
inline void split(int x,int y){
makeroot(x);//将x变为根
access(y);//将x-y联通
splay(y);//将y转到根
//这样做完我们就可以直接访问y来获得x-y路径的信息
}
- 操作
:连一条 的边
inline void link(int x,int y){
makeroot(x);
if(findroot(y)!=x) f[x]=y;//不在同一子树中,可以连边
}
- 操作
:断开 的边
inline void cut(int x,int y){
makeroot(x);
if(findroot(y)==x&&f[y]==x&&!l(y)){
//y所在的splay根必须为y且y的父亲必须为x
//y没有左儿子,因为若有左儿子说明dep[x]<dep[c[y][0]]<dep[y],那x,y之间就没有边
f[y]=r(x)=0;
push_up(x);
}
}
注意: 中 的操作与原先略有不同
【模板】动态树() #
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
int n,m,st[N];
int f[N],ch[N][2],v[N],s[N],lz[N];
namespace LCT{
#define l(x) ch[x][0]
#define r(x) ch[x][1]
inline bool rootful(int x){return l(f[x])!=x&&r(f[x])!=x;}
inline int chk(int x){return r(f[x])==x;}
inline void push_up(int x){s[x]=s[l(x)]^s[r(x)]^v[x];}
inline void rev(int x){swap(l(x),r(x));lz[x]^=1;}
inline void push_down(int x){
if(!lz[x]) return;
if(l(x)) rev(l(x));
if(r(x)) rev(r(x));
lz[x]=0;
}
inline void rotate(int x){
int y=f[x],z=f[y],k=chk(x),w=ch[x][k^1];
if(!rootful(y)) ch[z][chk(y)]=x;f[x]=z;//注意这里要判y是否为根
if(w) f[w]=y;ch[y][k]=w;
ch[x][k^1]=y,f[y]=x;
push_up(y),push_up(x);
}
inline void splay(int x){//把x转到当前splay的根
int y=x,top=0,z;
for(st[++top]=y;!rootful(y);st[++top]=y=f[y]);//暂存当前点->根的路径
for(;top;push_down(st[top--]));//这样就能从上到下释放懒标记
while(!rootful(x)){
y=f[x],z=f[y];
if(!rootful(y))
rotate(chk(x)==chk(y)?y:x);
rotate(x);
}
}
inline void access(int x){
for(int y=0;x;x=f[y=x]){
splay(x);
r(x)=y;
push_up(x);
}
}
inline void makeroot(int x){
access(x);
splay(x);
rev(x);
}
inline int findroot(int x){
access(x);splay(x);
for(;l(x);x=l(x)) push_down(x);
splay(x);
return x;
}
inline void split(int x,int y){
makeroot(x);
access(y);
splay(y);
}
inline void link(int x,int y){
makeroot(x);
if(findroot(y)!=x) f[x]=y;
}
inline void cut(int x,int y){
makeroot(x);
if(findroot(y)==x&&f[y]==x&&!l(y)){
f[y]=r(x)=0;
push_up(x);
}
}
}
signed main(){
n=read(),m=read();
for(int i=1;i<=n;++i) v[i]=read();
while(m--){
int op=read(),x=read(),y=read();
if(op==0){
LCT::split(x,y);
cout<<s[y]<<endl;
}
if(op==1) LCT::link(x,y);
if(op==2) LCT::cut(x,y);
if(op==3){
LCT::splay(x);//先把x旋到当前splay的根,用来更新懒标记
v[x]=y;
}
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通