LCT(Link-Cut-Tree)

LCT维护一个森林,即把每个节点用splay维护,可以进行许多操作:

  • 查询、修改链上的信息

  • 随意指定原树的根(即换根)

  • 动态连边、删边

  • 合并两棵树、分离一棵树

  • 动态维护连通性

主要性质

  1. 每一个Splay维护的是一条从上到下按在原树中深度严格递增的路径,且中序遍历Splay得到的每个点的深度序列严格递增。
  2. 每个节点仅包含于一个splay中。
  3. 边分为实边和虚边,实边记录 sonfa,包含在一个 splay 中。为了维护 splay 树形,虚边仅记录 fa。不过虚边是由 splay(根) 指向父亲的,不一定是原节点。

操作

access

access 操作是指将一个点到树根的路径打通,即把根节点和该节点搞到一个 splay 上。

我们从 x 向上爬。

  • 每次将所在节点 splay(转到 splay 的根节点)
  • 将该 splay 所指向的节点的儿子换为 splay 的根节点。
  • 更新信息。
  • 将操作点切换到父节点,重复操作直到节点的父亲是0。
void access(int x){
for(int y=0;x;y=x,x=fa[x]){
splay(x);son[x][1]=y;pushup(x);
}
}

makert

makert 操作可以将一个节点变成整棵树的根。

  • 将该节点 access
  • 将该节点 splay
  • 将该节点打上子树翻转标记。

正确性为,access 操作后将该节点到原来的根的路径打通并成为一个 splay 后,整条路径的 dfs 序都会反转,而其他节点的 dfs 序都不会变。

inline void rev(const int &x){tag[x]^=1,swap(son[x][0],son[x][1]);}
void makert(int x){access(x),splay(x),rev(x);}

findrt

findrt 操作可以找到一个节点在其树内的根。

  • 将该节点 access
  • 将该节点 splay
  • 一直跳左儿子,则找到 dfs 序最小的节点,也就是根。
int findrt(int x){access(x),splay(x);while(son[x][0])x=son[x][0];splay(x);return x;}

注意,上面的代码中如果不在找到根后 splay 复杂度是假的。

link

link 操作将两个连通块进行连边。

  • 若要在连边之前判断两者是否已经联通,可以将一个节点变成根,查找另一个节点的根进行判断。
  • 一般连边是将一个节点变成另一个节点的虚儿子,也就是连虚边。这种方式适用于虚儿子贡献较为简单计算的情况。设这两个节点为 x 和 y,我们将 y makert ,将 x splay,然后将 y 的 fa 改成 x 即可。(如果要统计子树信息的话,将两个节点都改为根,然后连边时顺便统计字数贡献)
  • 当然也可以直接连成实边。
void link(int x,int y){
makert(x);
if(findrt(y)!=x) fa[x]=y;
}
inline void link(int x,int y){
splay(x);fa[x]=y;
access(y),splay(y);
son[y][1]=x;pushup(y);
}

cut

cut 操作将两个点间进行删边。

  • 若要判断两个点原先是否有边相连,先将一个节点设成根然后判断连通性,再判断两点间的 dfs 序是否连续。
  • 然后直接将上面节点的儿子和下面节点的父亲设为 0 即可。别忘了更新信息。
inline void cut(int x,int y){
makert(x);
if(findrt(y)==x and fa[y]==x and !son[y][0]) rs=fa[y]=0,pushup(x);
}

模板

维护链上最大值。

struct LCT{
#define ls son[x][0]
#define rs son[x][1]
int tag[maxm],fa[maxm],st[maxm],mx[maxm],id[maxm],son[maxm][2];
inline bool notrt(int x){return son[fa[x]][0]==x or son[fa[x]][1]==x;}
inline int getw(int x){return son[fa[x]][1]==x;}
inline void rev(int x){if(x)swap(ls,rs),tag[x]^=1;}
inline void pushup(int x){
if(mx[ls]>mx[rs])mx[x]=mx[ls],id[x]=id[ls];
else mx[x]=mx[rs],id[x]=id[rs];
if(val[x]>mx[x])mx[x]=val[x],id[x]=x;
}
inline void pushdown(int x){if(tag[x])tag[x]=0,rev(ls),rev(rs);}
inline void rotate(int x){
int y=fa[x],z=fa[y],w=getw(x),s=son[x][!w];
if(notrt(y))son[z][getw(y)]=x;
son[x][!w]=y;son[y][w]=s;
if(s)fa[s]=y;fa[x]=z,fa[y]=x;
pushup(y);pushup(x);
}
inline void splay(int x){
int y,top=1;
for(y=x;notrt(st[++top]=y);y=fa[y]);
while(top)pushdown(st[top--]);
while(notrt(x)){
y=fa[x];
if(notrt(y)) rotate((getw(x)^getw(y))?x:y);
rotate(x);
}
pushup(x);
}
inline void access(int x){
for(int y=0;x;y=x,x=fa[x])
splay(x),rs=y,pushup(x);
}
inline int findroot(int x){
access(x),splay(x);
while(ls)x=ls;
splay(x);
return x;
}
inline void makeroot(int x){access(x),splay(x),rev(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)fa[x]=y;}
inline void cut(int x,int y){
makeroot(x);
if(findroot(y)==x and fa[y]==x and !son[y][0])
fa[y]=rs=0,pushup(x);
}
#undef ls
#undef rs
}L;

进阶

维护子树信息

LCT 可以维护子树信息,但是只能做到查询而做不到修改。简单来说,维护的方式就是每次给一个 splay 添加一个虚儿子的时候,需要多开一个数据结构记录虚儿子的贡献。然后在上传的时候考虑虚儿子即可。

P4219 [BJOI2014]大融合

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cctype>
#include<cstring>
#include<cmath>
using namespace std;
inline int read(){
int w=0,x=0;char c=getchar();
while(!isdigit(c))w|=c=='-',c=getchar();
while(isdigit(c))x=x*10+(c^48),c=getchar();
return w?-x:x;
}
namespace star
{
const int maxn=1e5+10;
int n,m;
struct LCT{
#define ls son[x][0]
#define rs son[x][1]
int tag[maxn],son[maxn][2],fa[maxn],siz[maxn],siz2[maxn],st[maxn];
inline bool getw(int x){return son[fa[x]][1]==x;}
inline void rev(int x){if(x)tag[x]^=1,swap(ls,rs);}
inline void pushup(int x){siz[x]=siz[ls]+siz[rs]+siz2[x]+1;}
inline void pushdown(int x){if(tag[x])tag[x]=0,rev(ls),rev(rs);}
inline bool notrt(int x){return son[fa[x]][0]==x or son[fa[x]][1]==x;}
inline void rotate(int x){
int y=fa[x],z=fa[y],w=getw(x),s=son[x][!w];
if(notrt(y))son[z][getw(y)]=x;son[y][w]=s;son[x][!w]=y;
if(s)fa[s]=y;fa[y]=x,fa[x]=z;
pushup(y);
}
inline void splay(int x){
int y;int top=0;
for(y=x;notrt(st[top++]=y);y=fa[y]);
while(top--)pushdown(st[top]);
while(notrt(x)){
y=fa[x];
if(notrt(y)) rotate(getw(x)^getw(y)?x:y);
rotate(x);
}
pushup(x);
}
inline void access(int x){for(int y=0;x;y=x,x=fa[x])splay(x),siz2[x]+=siz[rs]-siz[y],rs=y,pushup(x);}
inline void makert(int x){access(x),splay(x),rev(x);}
inline int findrt(int x){access(x),splay(x);while(ls)x=ls;splay(x);return x;}
inline void split(int x,int y){makert(x);access(y),splay(y);}
inline void link(int x,int y){makert(x);if(findrt(y)!=x)fa[x]=y,siz2[y]+=siz[x],splay(y);}
inline void cut(int x,int y){
makert(x);
if(findrt(y)==x and fa[y]==x and !son[y][0]) rs=fa[y]=0,pushup(x);
}
#undef ls
#undef rs
}L;
inline void work(){
n=read(),m=read();
int x,y;
while(m--){
char c=getchar();
while(!isalpha(c))c=getchar();
if(c=='A')L.link(read(),read());
else L.split(x=read(),y=read()),printf("%lld\n",1ll*(L.siz2[x]+1)*(L.siz2[y]+1));
}
}
}
signed main(){
star::work();
return 0;
}

动态求LCA

LCT 本来就是动态的,如何求两个点的 LCA 呢?

将其中一个点 access ,然后将另外一个点 access ,并记录最后一次 splay 前找到的节点(即最后的代码中的y)

利用LCT的结构

LCT 是一种优秀的暴力,它的结构有时候可以帮我们做一些很强的题目(虽然一般都想不到这个模型)

P3703 [SDOI2017]树点涂色

思路:观察操作,有“将一个点到根节点的路径染成同一种新的颜色”,发现同一颜色的连通块都是一条链,那么我们很快想到 LCT 的模型。维护的答案是该节点到根的 splay 个数。那么我们在改变 access 的时候,即改变儿子虚实的时候,需要将虚儿子子树内所有节点的答案都增加,将实儿子子树内所有节点都减少,这个可以用线段树进行维护。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cctype>
#include<cstring>
#include<cmath>
using namespace std;
inline int read(){
int w=0,x=0;char c=getchar();
while(!isdigit(c))w|=c=='-',c=getchar();
while(isdigit(c))x=x*10+(c^48),c=getchar();
return w?-x:x;
}
namespace star
{
const int maxn=1e5+10;
int n,m;
int dfn[maxn],dep[maxn],id[maxn],fa[maxn],son[maxn],siz[maxn],top[maxn];
int ecnt,head[maxn],nxt[maxn<<1],to[maxn<<1];
inline void addedge(int a,int b){
to[++ecnt]=b,nxt[ecnt]=head[a],head[a]=ecnt;
to[++ecnt]=a,nxt[ecnt]=head[b],head[b]=ecnt;
}
void dfs1(int x,int f){
fa[x]=f,dep[x]=dep[f]+1,siz[x]=1;
for(int u,i=head[x];i;i=nxt[i]) if((u=to[i])!=f){
dfs1(u,x);
siz[x]+=siz[u];
if(siz[u]>siz[son[x]]) son[x]=u;
}
}
void dfs2(int x,int topf){
top[x]=topf;dfn[x]=++dfn[0],id[dfn[0]]=x;
if(!son[x])return;
dfs2(son[x],topf);
for(int u,i=head[x];i;i=nxt[i]) if((u=to[i])!=fa[x] and u!=son[x]) dfs2(u,u);
}
inline int LCA(int x,int y){
while(top[x]!=top[y]) if(dep[top[x]]>dep[top[y]]) x=fa[top[x]];
else y=fa[top[y]];
return dep[x]<dep[y]?x:y;
}
struct SegmentTree{
#define ls (ro<<1)
#define rs (ro<<1|1)
#define mid ((l+r)>>1)
int mx[maxn<<2],tag[maxn<<2];
inline void pushup(const int &ro){mx[ro]=max(mx[ls],mx[rs]);}
inline void pushdown(const int &ro){tag[ls]+=tag[ro],tag[rs]+=tag[ro];mx[ls]+=tag[ro],mx[rs]+=tag[ro];tag[ro]=0;}
void build(const int &ro=1,const int &l=1,const int &r=n){
if(l==r)return mx[ro]=dep[id[l]],tag[ro]=0,void();
build(ls,l,mid),build(rs,mid+1,r);
pushup(ro);
}
void update(const int &x,const int &y,const int &k,const int &ro=1,const int &l=1,const int &r=n){
if(x==l and y==r) return tag[ro]+=k,mx[ro]+=k,void();
if(tag[ro])pushdown(ro);
if(y<=mid) update(x,y,k,ls,l,mid);
else if(x>mid) update(x,y,k,rs,mid+1,r);
else update(x,mid,k,ls,l,mid),update(mid+1,y,k,rs,mid+1,r);
pushup(ro);
}
int query(const int &x,const int &y,const int &ro=1,const int &l=1,const int &r=n){
if(x==l and y==r)return mx[ro];
if(tag[ro])pushdown(ro);
if(y<=mid) return query(x,y,ls,l,mid);
if(x>mid) return query(x,y,rs,mid+1,r);
return max(query(x,mid,ls,l,mid),query(mid+1,y,rs,mid+1,r));
}
#undef ls
#undef rs
#undef mid
}T;
struct LCT{
#define ls son[x][0]
#define rs son[x][1]
int son[maxn][2],fa[maxn];
inline bool notrt(int x){return son[fa[x]][0]==x or son[fa[x]][1]==x;}
inline int getw(int x){return son[fa[x]][1]==x;}
inline void rotate(int x){
int y=fa[x],z=fa[y],w=getw(x),s=son[x][!w];
if(notrt(y)) son[z][getw(y)]=x;son[y][w]=s,son[x][!w]=y;
if(s) fa[s]=y;fa[y]=x,fa[x]=z;
}
inline void splay(int x){
while(notrt(x)){
int y=fa[x];
if(notrt(y))rotate(getw(x)^getw(y)?x:y);
rotate(x);
}
}
inline int findrt(int x){while(ls)x=ls;return x;}
inline void access(int x){
for(int u,y=0;x;y=x,x=fa[x]){
splay(x);
if(rs) u=findrt(rs),T.update(dfn[u],dfn[u]+siz[u]-1,1);
if(rs=y) u=findrt(rs),T.update(dfn[u],dfn[u]+siz[u]-1,-1);
}
}
#undef ls
#undef rs
}S;
inline void work(){
n=read(),m=read();
for(int i=1;i<n;i++) addedge(read(),read());
dfs1(1,0);dfs2(1,1);
for(int i=1;i<=n;i++) S.fa[i]=fa[i];
T.build();
while(m--)
switch(read()){
case 1:S.access(read());break;
case 2:{
int x=read(),y=read(),lca=LCA(x,y);
printf("%d\n",T.query(dfn[x],dfn[x])+T.query(dfn[y],dfn[y])-2*T.query(dfn[lca],dfn[lca])+1);
break;
}
case 3:{
int x=read();
printf("%d\n",T.query(dfn[x],dfn[x]+siz[x]-1));
}
}
}
}
signed main(){
star::work();
return 0;
}

P6292 区间本质不同子串个数也用到了这个 trick。

更多trick

待耕。

LCT(Link-Cut-Tree)的更多相关文章

  1. LCT(link cut tree) 动态树

    模板参考:https://blog.csdn.net/saramanda/article/details/55253627 综合各位大大博客后整理的模板: #include<iostream&g ...

  2. 【学习笔记】LCT link cut tree

    大概就是供自己复习的吧 1. 细节讲解 安利两篇blog: Menci 非常好的讲解与题单 2.模板 把 $ rev $ 和 $ pushdown $ 的位置记清 #define lc son[x][ ...

  3. LCT总结——概念篇+洛谷P3690[模板]Link Cut Tree(动态树)(LCT,Splay)

    为了优化体验(其实是强迫症),蒟蒻把总结拆成了两篇,方便不同学习阶段的Dalao们切换. LCT总结--应用篇戳这里 概念.性质简述 首先介绍一下链剖分的概念(感谢laofu的讲课) 链剖分,是指一类 ...

  4. 洛谷P3690 [模板] Link Cut Tree [LCT]

    题目传送门 Link Cut Tree 题目背景 动态树 题目描述 给定n个点以及每个点的权值,要你处理接下来的m个操作.操作有4种.操作从0到3编号.点从1到n编号. 0:后接两个整数(x,y),代 ...

  5. LuoguP3690 【模板】Link Cut Tree (动态树) LCT模板

    P3690 [模板]Link Cut Tree (动态树) 题目背景 动态树 题目描述 给定n个点以及每个点的权值,要你处理接下来的m个操作.操作有4种.操作从0到3编号.点从1到n编号. 0:后接两 ...

  6. P3690 【模板】Link Cut Tree (动态树)

    P3690 [模板]Link Cut Tree (动态树) 认父不认子的lct 注意:不 要 把 $fa[x]$和$nrt(x)$ 混 在 一 起 ! #include<cstdio> v ...

  7. Link Cut Tree学习笔记

    从这里开始 动态树问题和Link Cut Tree 一些定义 access操作 换根操作 link和cut操作 时间复杂度证明 Link Cut Tree维护链上信息 Link Cut Tree维护子 ...

  8. Link Cut Tree 总结

    Link-Cut-Tree Tags:数据结构 ##更好阅读体验:https://www.zybuluo.com/xzyxzy/note/1027479 一.概述 \(LCT\),动态树的一种,又可以 ...

  9. 【刷题】洛谷 P3690 【模板】Link Cut Tree (动态树)

    题目背景 动态树 题目描述 给定n个点以及每个点的权值,要你处理接下来的m个操作.操作有4种.操作从0到3编号.点从1到n编号. 0:后接两个整数(x,y),代表询问从x到y的路径上的点的权值的xor ...

  10. Luogu 3690 Link Cut Tree

    Luogu 3690 Link Cut Tree \(LCT\) 模板题.可以参考讲解和这份码风(个人认为)良好的代码. 注意用 \(set\) 来维护实际图中两点是否有直接连边,否则无脑 \(Lin ...

随机推荐

  1. 『言善信』Fiddler工具 — 8、Fiddler检查器(Inspectors)详解

    目录 1.请求报文内容 2.响应报文内容 3.响应报文中Transformer选项说明 Inspectors意思是检查器.Inspectors可以使用多种方式,查看请求的请求报文和响应报文相关信息. ...

  2. Hive窗口函数保姆级教程

    在SQL中有一类函数叫做聚合函数,例如sum().avg().max()等等,这类函数可以将多行数据按照规则聚集为一行,一般来讲聚集后的行数是要少于聚集前的行数的.但是有时我们想要既显示聚集前的数据, ...

  3. 谷歌:python速成课程笔记

    1.从用户那里获取信息 name = "Alex" print("hello" + name) 2.让python成为你的计算器 1 print(4+5) 2 ...

  4. 懒人 IDEA 插件推荐:EasyCode 一键帮你生成所需代码

    Easycode是idea的一个插件,可以直接对数据的表生成entity,controller,service,dao,mapper,无需任何编码,简单而强大. 1.安装(EasyCode) 我这里的 ...

  5. 01 . Varnish简介,原理,配置缓存

    简介 Varnish是高性能开源的反向代理服务器和HTTP缓存服务器,其功能与Squid服务器相似,都可以用来做HTTP缓存.可以安装 varnish 在任何web前端,同时配置它缓存内容.与传统的 ...

  6. 【TCP/IP】TCP详解笔记

    目录 前言 17. TCP 传输控制协议 17.1 引言 17.2 TCP 服务 17.3 TCP的首部 18. TCP连接的建立与终止 18.1 引言 18.2 连接的建立与终止 18.2.1 建立 ...

  7. 题解 P2257 YY的GCD

    P2257 YY的GCD 解题思路 果然数论的题是真心不好搞. 第一个莫比乌斯反演的题,好好推一下式子吧..(借鉴了blog) 我们要求的答案就是\(Ans=\sum\limits_{i=1}^{n} ...

  8. Linux中查看网络命令

    tcp三次握手,所以一直在listening,在等待信号 udp是没有listening状态的,因为不管你在不在都会发信息给你. netstat -r  =route -n  可以查看路由

  9. 32.qt quick-PathView实现好看的home界面

    pathView的使用类似与ListView,都需要模型(model)和代理(delegate),只不过pathView多了一个路径(path)属性,顾名思义路径就是item滑动的路径. 一个Path ...

  10. 8、linux常用命令

    8.1.pwd: 显示当前的路径: -L:显示逻辑路径,即快捷方式的路径(默认的参数): -P :显示物理路径,真实的路径: 8.2.man: 命令的查看: 8.3.help: 命令的查看: 8.4. ...