LCT学习笔记

LCT学习笔记

LCT,(Link Cut Tree) ,是一种动态树,维护的是森林。可以维护作有两点连通性,两点路径权值和,连接两点和切断某条边、修改信息等。

LCT是用Splay来维护的。每颗Splay维护的是原森林中的一条路径。

  • 性质一:Splay还要维护一个性质,那就是每颗Splay的中序遍历是原树中的一条从上到下的路径。(也就是深度递增的序列)再具体来说,Splay中的每个节点,左儿子是他的父亲,右儿子是他的儿子,如果他是父亲的左儿子,那他就是深度更深的节点(并不一定是直接的)。

而Splay是用实链剖分来划分的。也就是每个点向他的某个儿子连实边,向其它的儿子连虚边。

  • 性质二:每颗Splay并不是独立的,每颗Splay的根节点指向原树中这条链的父亲节点(即原树中这条链的最顶端的父亲节点)。需要注意的是,这连接的是虚边。

算了,不说这么多,我也不太理解,直接讲代码把。

Code

#include<bits/stdc++.h> using namespace std; const int N=400011; struct node{ int fa,ch[2],val,sum,tag; }t[N]; int isroot(int u){ return t[t[u].fa].ch[0]==u||t[t[u].fa].ch[1]==u; //判断一个点是不是这颗Splay的根节点 } void pushup(int u){ t[u].sum=t[t[u].ch[0]].sum^t[t[u].ch[1]].sum^t[u].val; //上传节点信息 } void rever(int u){ swap(t[u].ch[0],t[u].ch[1]); t[u].tag^=1; //旋转一个节点 } int get_son(int u){ return t[t[u].fa].ch[1]==u; //判断一个点是他的父亲节点的那个儿子 } void pushdown(int u){ if(t[u].tag){ if(t[u].ch[0])rever(t[u].ch[0]); if(t[u].ch[1])rever(t[u].ch[1]); t[u].tag=0; } //下传标记 } void rotate(int u){ int Y=t[u].fa; int R=t[Y].fa; int Yson=get_son(u); int B=t[u].ch[!Yson]; if(isroot(Y))t[R].ch[get_son(Y)]=u; t[Y].ch[Yson]=B; t[u].ch[!Yson]=Y; if(B)t[B].fa=Y; t[Y].fa=u; t[u].fa=R; pushup(Y); pushup(u); //旋转一个节点 } int sta[N]; void splay(int u){ int x=u,top=0; sta[++top]=x; while(isroot(x)){ x=t[x].fa; sta[++top]=x; } // while(isroot(x))sta[++top]=x=t[x].fa; // while(top)pushdown(sta[top--]); while(top){ pushdown(sta[top]); top--; } while(isroot(u)){//这个点还有父亲 int fa=t[u].fa; if(isroot(fa)){ if(get_son(u)==get_son(fa))rotate(fa); else rotate(u); } rotate(u); } //将一个节点旋转到根节点 } void access(int u){ for(int x=0;u;x=u,u=t[u].fa){ splay(u);t[u].ch[1]=x;pushup(u); } //将一个点与原树的根节点的路径打通 } void makeroot(int u){ access(u); splay(u); rever(u); //将一个点变成根节点, //先将这个点与根节点的路径打通, //通过access的过程可以知道,u不会有右儿子,且一直会是父亲的右儿子 //所以他的中序遍历是最后一个,所以将他翻转一下就是第一个 } int findroot(int u){ access(u); splay(u); while(t[u].ch[0])pushdown(u),u=t[u].ch[0]; //一定要记得pushdown,所有向下的操作都是要pushdown的 splay(u);//将根旋转回去 return u; //看 u 所在的数的根是那个 //先将他提到根节点,然后一直往左儿子走就好了 } void split(int x,int y){ makeroot(x); access(y); splay(y); //将x到y的路径提出来,现将x变成根,y跟x联通后将y提到根节点就好 //x到y的路径会变成一条实链,且这条实链也只包括x到y的路径 //因为虚链认父不认子,所以只会包括这条链的信息 } void link(int x,int y){ makeroot(x); if(findroot(y)!=x)t[x].fa=y; //连接 x y //现将x提到根节点,再查询一下, //不联通的话,直接将x的父亲设为y就好 ,因为x是根节点,所以没有影响 } void cut(int x,int y){ makeroot(x); if(findroot(y)==x&&t[y].fa==x&&t[y].ch[0]==0){ t[y].fa=t[x].ch[1]=0; pushup(x); } //解释一下为什么findroot不会改变树的结构 //因为要满足要求的话,在makeroot(x) 的时候,x就已经是 y的父亲 // //删除x到y的边 //也是先将x提到根节点,要满足x到y之间有边的话, //一定会满足y的跟是x,y的父亲是x,y没有左儿子 //性质1显然 //性质2因为x,y之间有边,并且x被提到了根,所以y深度一定比x大,所以y一定x的儿子 //性质3 y也不能有左儿子,因为有左儿子就说明有点的深度比y小,这显然是不满足性质的 } int main(){ int n,m; cin>>n>>m; for(int i=1;i<=n;i++)cin>>t[i].val; for(int i=1;i<=m;i++){ int opt,x,y; cin>>opt>>x>>y; if(opt==0){ split(x,y); cout<<t[y].sum<<"\n"; } if(opt==1){ link(x,y); } if(opt==2){ cut(x,y); } if(opt==3){ splay(x); t[x].val=y; } } }

应用

LCT求LCA

虽然感觉有点大材小用了,但LCT确实是可以求LCA的,而且还可以求动态的LCA。即可以支持动态加边,换根等LCT本生支持的操作。

其他的操作都保存,唯一要加的就是一个求LCA的函数。

我们先将 x 和根节点连起来。然后显然,y和x的LCA一定在x到根节点的这条实链上。我们保存access的操作,最后一个连向的节点,就是他们相交的节点,就是他们的LCA。

int lca(int x,int y){ access(x); int lst; for(lst=0;y;lst=y,y=t[y].fa){ splay(y);t[y].ch[1]=lst; } return lst; }

复杂度的话,LCT的常数显然是很大的,测试后大概跟倍增的常数(实现比较优秀的倍增)一致,是树剖的一半。

码量不长,但是绝对算不上好调。平时还是没有太大必要写的。

维护链信息(LCT上的线段树操作)

P1501 [国家集训队] Tree II

支持的操作:加边,删边,链上加,链上乘,链上求和。

还记得LCT的 split 操作吗,很显然,我们只需要将这条链扒出来,然后对这条链进行正常的标记修改就好了。也就是对这条链的顶点进行标记修改就好。跟普通的线段树写法没有任何区别。

唯一跟普通LCT相比需要修改的就是 pushdown 要跟线段树一样下放标记就好。

#include<bits/stdc++.h> using namespace std; const int N=200011; #define int long long int n,q; #define ls t[u].ch[0] #define rs t[u].ch[1] #define mod 51061 struct node{ int fa,siz,taf,ch[2]; int add,mul,sum,val,tag; }t[N]; int get_son(int u){ return t[t[u].fa].ch[1]==u; } void rever(int u){ swap(t[u].ch[0],t[u].ch[1]); t[u].tag^=1; } int isroot(int u){ return t[t[u].fa].ch[0]==u||t[t[u].fa].ch[1]==u; } void pushup(int u){ t[u].sum=(t[ls].sum+t[rs].sum+t[u].val)%mod; t[u].siz=t[ls].siz+t[rs].siz+1; } void pushdown(int u){ if(t[u].tag){ rever(ls); rever(rs); t[u].tag=0; } if(t[u].mul!=1){ t[ls].mul=t[ls].mul*t[u].mul%mod; t[rs].mul=t[rs].mul*t[u].mul%mod; t[ls].add=t[ls].add*t[u].mul%mod; t[rs].add=t[rs].add*t[u].mul%mod; t[ls].val=t[ls].val*t[u].mul%mod; t[rs].val=t[rs].val*t[u].mul%mod; t[ls].sum=t[ls].sum*t[u].mul%mod; t[rs].sum=t[rs].sum*t[u].mul%mod; t[u].mul=1; } if(t[u].add){ t[ls].add=(t[ls].add+t[u].add)%mod; t[rs].add=(t[rs].add+t[u].add)%mod; t[ls].val=(t[ls].val+t[u].add)%mod; t[rs].val=(t[rs].val+t[u].add)%mod; t[ls].sum=(t[ls].sum+t[ls].siz*t[u].add)%mod; t[rs].sum=(t[rs].sum+t[rs].siz*t[u].add)%mod; t[u].add=0; } } void rotate(int u){ int Y=t[u].fa; int R=t[Y].fa; int Yson=get_son(u); int Rson=get_son(Y); int B=t[u].ch[!Yson]; if(isroot(Y))t[R].ch[Rson]=u; t[u].fa=R; t[B].fa=Y; t[Y].ch[Yson]=B; t[u].ch[!Yson]=Y; t[Y].fa=u; pushup(Y); pushup(u); } int sta[N]; void splay(int u){ int top=0,x=u; sta[++top]=x; while(isroot(x)){ x=t[x].fa; sta[++top]=x; } while(top){ pushdown(sta[top]); top--; } while(isroot(u)){ int fa=t[u].fa; if(isroot(fa)){ if(get_son(u)==get_son(fa))rotate(fa); else rotate(u); } rotate(u); } } void access(int u){ for(int x=0;u;x=u,u=t[u].fa){ splay(u);t[u].ch[1]=x;pushup(u); } } int findroot(int u){ access(u); splay(u); while(t[u].ch[0])pushdown(u),u=t[u].ch[0]; splay(u); return u; } void makeroot(int u){ access(u); splay(u); rever(u); } void link(int x,int y){ makeroot(x); if(findroot(y)!=x)t[x].fa=y; } void cut(int x,int y){ makeroot(x); if(findroot(y)==x&&t[y].fa==x&&t[y].ch[0]==0){ t[y].fa=t[x].ch[1]=0; pushup(x); } } void split(int x,int y){ makeroot(x); access(y); splay(y); } signed main(){ cin>>n>>q; for(int i=1;i<=n;i++)t[i].siz=t[i].val=t[i].sum=t[i].mul=1; for(int i=1;i<n;i++){ int u,v; cin>>u>>v; link(u,v); } for(int i=1;i<=q;i++){ char opt; int u,v,c,x,y; cin>>opt; if(opt=='+'){ cin>>u>>v>>c; split(u,v); t[v].add=(t[v].add+c)%mod; t[v].sum=(t[v].sum+t[v].siz*c)%mod; t[v].val=(t[v].val+c)%mod; } if(opt=='*'){ cin>>u>>v>>c; split(u,v); t[v].mul=t[v].mul*c%mod; t[v].sum=t[v].sum*c%mod; t[v].val=t[v].val*c%mod; } if(opt=='/'){ cin>>u>>v; split(u,v); cout<<t[v].sum<<"\n"; } if(opt=='-'){ cin>>u>>v>>x>>y; cut(u,v); link(x,y); } } }

P3203 [HNOI2010] 弹飞绵羊

弹力系数可以看做一条边,答案就是走到外面的点的边数。

考虑建立一个 n+1 号节点,答案就是从那条边就到 n+1 号节点的边数。

显然可以先把 n+1 号点到 i 号节点的边扒出来。答案就是这条路径的长度。这就很简单了。

#include<bits/stdc++.h> using namespace std; const int N=400011; struct node{ int siz,ch[2],fa,tag; }t[N]; int get_son(int u){ return t[t[u].fa].ch[1]==u; } int isroot(int u){ return t[t[u].fa].ch[0]==u||t[t[u].fa].ch[1]==u; } #define ls t[u].ch[0] #define rs t[u].ch[1] void rever(int u){ swap(t[u].ch[0],t[u].ch[1]); t[u].tag^=1; } void pushdown(int u){ if(t[u].tag){ rever(t[u].ch[0]); rever(t[u].ch[1]); t[u].tag=0; } } void pushup(int u){ t[u].siz=t[ls].siz+t[rs].siz+1; } void rotate(int u){ int Y=t[u].fa; int R=t[Y].fa; int Yson=get_son(u); int Rson=get_son(Y); int B=t[u].ch[!Yson]; if(isroot(Y))t[R].ch[Rson]=u; t[u].fa=R; t[Y].ch[Yson]=B; t[B].fa=Y; t[u].ch[!Yson]=Y; t[Y].fa=u; pushup(Y); pushup(u); } int sta[N]; void splay(int u){ int x=u,top=0; sta[++top]=x; while(isroot(x)){ x=t[x].fa; sta[++top]=x; } while(top){ pushdown(sta[top]); top--; } while(isroot(u)){ int fa=t[u].fa; if(isroot(fa)){ if(get_son(u)==get_son(fa))rotate(fa); else rotate(u); } rotate(u); } } void access(int u){ for(int x=0;u;x=u,u=t[u].fa){ splay(u);t[u].ch[1]=x;pushup(u); } } int findroot(int u){ access(u); splay(u); while(t[u].ch[0])pushdown(u),u=t[u].ch[0]; splay(u); return u; } void makeroot(int u){ access(u); splay(u); rever(u); } void link(int x,int y){ makeroot(x); if(findroot(y)!=x)t[x].fa=y; } void cut(int x,int y){ makeroot(x); if(findroot(y)==x&&t[y].fa==x&&t[y].ch[0]==0){ t[y].fa=t[x].ch[1]=0; pushup(x); } } void split(int x,int y){ makeroot(x); access(y); splay(y); } int n,m,x[N]; int main(){ ios::sync_with_stdio(0); cin.tie(0);cout.tie(0); cin>>n; for(int i=1;i<=n+1;i++)t[i].siz=1; for(int i=1;i<=n;i++){ cin>>x[i]; if(i+x[i]>n){ link(i,n+1); // cout<<"link:"<<i<<" "<<n+1<<'\n'; } else{ link(i,i+x[i]); // cout<<"link:"<<i<<" "<<i+x[i]<<"\n"; } } cin>>m; for(int i=1;i<=m;i++){ int opt,j,k; cin>>opt>>j; j++; if(opt==1){ split(j,n+1); cout<<t[n+1].siz-1<<"\n"; } else{ cin>>k; cut(j,(j+x[j]>n?n+1:j+x[j])); x[j]=k; link(j,(j+x[j]>n?n+1:j+x[j])); } } }

动态维护连通性&双联通分量

P3950 部落冲突

就是随便判一下连通性就好。判断一下根是不是就好。

维护链上的边权信息(生成树)

维护子树信息

P4219 [BJOI2014] 大融合

一条边的贡献很好求,就是他们的子树大小相乘。

所以问题就变成了求动态加边的情况下求子树大小。

这就需要维护子树信息的LCT了。

因为还有虚子树的存在,所以我们需要维护一个虚子树的信息。

设原子树的信息为 siz[x] ,虚子树的信息为 siz2[x]

考虑什么时候会改变边的虚实关系。

发现在 access 操作的时候会修改一个点的右儿子。这是我们就需要对他的 siz2[x] 进行修改。具体如下

void access(int u){ for(int x=0;u;x=u,u=t[u].fa){ splay(u);siz2[u]+=siz[t[u].ch[1]]-siz[x];pushup(u); } }

然后就是在加边的时候也会影响到一个点的虚实关系。 link(x,y) 的话会使得 y 多一条虚边。这是可以将 ysiz2[y]+=siz[x] 就好。

然后其他的操作就跟普通的 LCT 一致就好。

#include<bits/stdc++.h> using namespace std; const int N=100011; int n,q; #define int long long struct tree{ int ch[2],siz,siz2,tag,fa; }t[N]; int get_son(int u){ return t[t[u].fa].ch[1]==u; } int isroot(int u){ return t[t[u].fa].ch[0]==u||t[t[u].fa].ch[1]==u; } #define ls t[u].ch[0] #define rs t[u].ch[1] void rever(int u){ swap(ls,rs); t[u].tag^=1; } void pushdown(int u){ if(t[u].tag){ rever(ls); rever(rs); t[u].tag^=1; } } void pushup(int u){ t[u].siz=t[ls].siz+t[rs].siz+t[u].siz2+1; } void rotate(int u){ int Y=t[u].fa; int R=t[Y].fa; int Yson=get_son(u); int Rson=get_son(Y); int B=t[u].ch[!Yson]; if(isroot(Y))t[R].ch[Rson]=u; t[u].fa=R; t[Y].fa=u; t[u].ch[!Yson]=Y; t[B].fa=Y; t[Y].ch[Yson]=B; pushup(Y); pushup(u); } int top=0,sta[N]; void splay(int u){ int x=u; sta[++top]=x; while(isroot(x)){ x=t[x].fa; sta[++top]=x; } while(top){ pushdown(sta[top]); top--; } while(isroot(u)){ int fa=t[u].fa; if(isroot(fa)){ if(get_son(u)==get_son(fa))rotate(fa); else rotate(u); } rotate(u); } } void access(int u){ for(int x=0;u;x=u,u=t[u].fa){ splay(u); t[u].siz2+=t[t[u].ch[1]].siz-t[x].siz; t[u].ch[1]=x; pushup(u); } } void makeroot(int u){ access(u); splay(u); rever(u); } void link(int x,int y){ makeroot(x); makeroot(y); t[x].fa=y; t[y].siz2+=t[x].siz; pushup(y); } void cut(int x,int y){ makeroot(x); access(y); splay(x); t[x].ch[1]=t[y].fa=0; pushup(y);pushup(x); } signed main(){ cin>>n>>q; for(int i=1;i<=n;i++)t[i].siz=1; for(int i=1;i<=q;i++){ char op; int x,y; cin>>op>>x>>y; if(op=='A'){ link(x,y); } else{ cut(x,y); makeroot(x); int ans1=t[x].siz; makeroot(y); int ans2=t[y].siz; link(x,y); cout<<ans1*ans2<<"\n"; } } }

维护树上染色联通块

特殊题型


__EOF__

本文作者shAdom
本文链接https://www.cnblogs.com/shadom/p/17824367.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   shAdomOvO  阅读(79)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
点击右上角即可分享
微信分享提示