Link Cut Tree(动态树)
概念讲解
LCT维护的对象其实是一个森林。
在实链剖分的基础下,LCT支持更多的操作,即树剖升级版,但在实际做题中因为树剖的常数小且相对容易调试,所以能用树剖就尽量用树剖。
- 查询、修改链上的信息(最值,总和,异或和等)
- 随意指定原树的根(即换根)
- 动态连边、删边
- 合并两棵树、分离一棵树(跟上面不是一毛一样吗)
- 动态维护连通性
- 更多操作
操作
access(x)
作用: 把从根到 x 的所有点放在一条实链里,使根到 x 成为一条实路径,并且在同一棵 Splay 里
代码实现:(循环处理)
- 转到根
- 换儿子
- 更新信息
- 操作点切换为轻边所指的父亲,然后进行操作1
void access(int x){
for(int y=0;x;y=x,x=fa[x]){
splay(x);
ch[x][1]=y;
updat(y);
}
}
makeroot(x)
作用:使 x 点成为其所在树的根
void makeroot(int x){
access(x);
splay(x);
reverse(x);
}
split(x,y)
作用:提取出 x, y 间的路径,方便做区间操作
void split(int x,int y){
makeroot(x);
access(y);
splay(y);
}
link(x,y)
作用:在 x, y 两点间连一条边
void link(int x,int y){
makeroot(x);
fa[x]=y;
}
cut(x,y)
作用:把 x, y 两点间边删掉
void cut(int x,int y){
split(x,y);
if(ch[y][0]!=x||ch[x][1])return ;
fa[x]=ch[y][0]=0;
updat(y);
}
findroot(x)
作用:找到 x 所在树的根节点编号
int findroot(int x){
access(x);
splay(x);
while(ch[x][0])pushdown(x),x=ch[x][0];
splay(x);
return x;
}
模板:洛谷P3690
模板代码
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
struct node {
int fa,ch[2],sum,val,lazy;
} t[N]; //lazy用来标记reverse()的左右翻转
#define lc t[x].ch[0] //左儿子
#define rc t[x].ch[1] //右儿子
bool isRoot(int x) { //判断是否是splay根节点
int g=t[x].fa;
return t[g].ch[0]!=x && t[g].ch[1]!=x;//若为根,则父结点不应该有这个儿子
}
void pushup(int x) { //本题的求路径异或和。上传信息
t[x].sum=t[x].val^t[lc].sum^t[rc].sum;
}
void reverse(int x) {
if(!x)return;
swap(lc,rc); //翻转x的左右儿子
t[x].lazy^=1; //懒惰标记,先不翻转儿子的后代,后面再翻转
}
void pushdown(int x) { //递归翻转x的儿子的后代,并释放懒标记。
if(t[x].lazy) {
reverse(lc);
reverse(rc);
t[x].lazy=0;
}
}
void push(int x) {
if(!isRoot(x)) push(t[x].fa); //从根到x全部pushdown
pushdown(x);
}
void rotate(int x) {
int y=t[x].fa;
int z=t[y].fa;
int k=t[y].ch[1]==x;
if(!isRoot(y)) t[z].ch[t[z].ch[1]==y]=x;//这里要稍微注意
t[x].fa=z;
t[y].ch[k]=t[x].ch[k^1];
if(t[x].ch[k^1])t[t[x].ch[k^1]].fa=y;
t[y].fa=x;
t[x].ch[k^1]=y;
pushup(y);
}
void splay(int x) { //提根:把x旋转为它所在的Splay树的根
int y,z;
push(x); //先pushdown处理x的所有子孙的lazy标记
while(!isRoot(x)) {
y=t[x].fa,z=t[y].fa;
if(!isRoot(y))
(t[z].ch[0]==y)^(t[y].ch[0]==x)?rotate(x):rotate(y);
rotate(x);
}
pushup(x);
}
void access(int x) { //在原树上建一条实链,起点是根,终点是x
for(int child=0; x; child=x, x=t[x].fa) { //从x往上走,沿着虚边走到根
splay(x);
rc = child; //右孩子是child,建立了一条实边
pushup(x);//路径信息处理
if(child) t[child].fa=x;
}
}
void makeroot(int x) { //把x在原树上旋转到根的位置
//使节点x成为原树树根,该操作等价于将“根节点到节点x”路径上所有树边的方向取反
access(x);
splay(x);
reverse(x);//在原树发生变化
}
void split(int x,int y) { //把原树上以x为起点、y为终点的路径,生成一条实链
makeroot(x);
access(y);
splay(y);
}
void link(int x,int y) { //在结点x和y之间连接一条边
makeroot(x);
t[x].fa=y;
}
void cut(int x,int y) { //将x,y的边切断
split(x,y);
if(t[y].ch[0]!=x||rc) return;
t[x].fa=t[y].ch[0]=0;
pushup(y);
}
int findroot(int x) { //查找x在原树上的根,也可以直接判断两点的连通性。
access(x);
splay(x);
while(lc) pushdown(x),x=lc; //找Splay树最左端的结点
return x;
}
int main() {
int n,m;
scanf("%d%d",&n,&m);
for(int i=1; i<=n; ++i) {
scanf("%d",&t[i].val);
t[i].sum = t[i].val;
}
while(m--) {
int opt,a,b;
scanf("%d%d%d",&opt,&a,&b);
switch(opt) {
case 0:
split(a,b);
printf("%d\n",t[b].sum);
break;
case 1:
if(findroot(a) != findroot(b)) link(a,b);
break;
case 2:
cut(a,b);
break;
case 3:
splay(a);
t[a].val=b;
break;
}
}
return 0;
}
讲的比较好的博客
大佬的题单
我的[题单]
标签:access,ch,int,void,splay,fa,2.7,2.6 From: https://www.cnblogs.com/mkik/p/17095534.html