这一个月貌似已经考了无数次\(LCT\)了.....

保险起见还是来一发总结吧.....

A. LCT 模板

\(LCT\) 是由大名鼎鼎的 \(Tarjan\) 老爷发明的。

主要是用来维护树上路径问题的。 它的神奇之处在于可以直接把一条路径抠出来维护。

其实就是维护树链剖分中的重链与轻链。

网上相关教程很多,直接给板子(其实是我懒得打E_E)

0. Splay代码:

\(LCT\)是以\(Splay\) 为实现基础的:

  1. IL bool Son(RG int x){return ch[fa[x]][1] == x;}
  2. IL bool Isroot(RG int x){return ch[fa[x]][1]!=x && ch[fa[x]][0]!=x; }
  3. IL void Rot(RG int x){
  4. RG int y = fa[x] , z = fa[y] , c = Son(x);
  5. if(!Isroot(y))ch[z][Son(y)] = x; fa[x] = z;
  6. ch[y][c] = ch[x][!c]; fa[ch[y][c]] = y;
  7. ch[x][!c] = y; fa[y] = x; PushUp(y);
  8. }
  9. IL void Splay(RG int x){
  10. RG int top = 0; stk[++top] = x;
  11. for(RG int i = x; !Isroot(i); i = fa[i])stk[++top] = fa[i];
  12. while(top)PushDown(stk[top--]);
  13. for(RG int y = fa[x]; !Isroot(x) ; Rot(x) , y = fa[x])
  14. if(!Isroot(y))Son(x) ^ Son(y) ? Rot(x) : Rot(y);
  15. PushUp(x); return;
  16. }

1. Access(x) 操作

作用是把 \(x\) 到根节点的这条路径变为重链。

  1. void Access(int x){
  2. for(RG int y=0;x;y=x,x=fa[x])
  3. Splay(x) , ch[x][1] = y , PushUp(x);
  4. }
  5. }

2. MakeRoot(x) 操作

作用是使 \(x\) 节点成为原树中的根。

  1. IL void Makeroot(RG int x){Access(x); Splay(x); Reverse(x);}

对应的辅助函数:

  1. IL bool Reverse(RG int x){swap(ch[x][1],ch[x][0]); rev[x] ^= 1;}
  1. IL void PushDown(RG int x){
  2. if(rev[x])
  3. rev[x] ^= 1 ,
  4. Reverse(ch[x][0]) , Reverse(ch[x][1]) ;
  5. }

3. FindRoot(x) 操作

作用是找到\(x\)所在原树的根节点。

  1. IL int FindRoot(RG int x){
  2. Access(x); Splay(x);
  3. while(ch[x][0])x=ch[x][0]; return x;
  4. }

4. Split(x,y) 操作

作用是分离出\(x\)到\(y\)这条路径。

  1. IL void Split(RG int x,RG int y){MakeRoot(x); Access(y); Splay(y);}

那么此时路径信息都存在节点 \(y\) 上了,要查询什么直接查即可。

5. Link(x,y) 操作

作用是连接两个结点\(x\) , \(y\)

  1. IL void Link(RG int x,RG int y){if((!x)||(!y))return; MakeRoot(x); fa[x] = y; }

6. Cut(x,y) 操作

作用是删除\(x\) , \(y\) 之间的连边

  1. IL void Cut(RG int x,RG int y){if((!x)||(!y))return; Split(x,y); ch[y][0] = fa[x] = 0; PushUp(y);}

7. PushUp(x) \ PushDown(x) 操作

其实是与线段树一样的,直接维护题目所求即可。

代码略,以实际题目为准(注意翻转左右儿子的\(PushDown\)是必须有的)。

8.贴一发完整的\(LCT\)模版


  1. namespace Link_Cut_Tree{
  2. bool Son(RG int x){return ch[fa[x]][1] == x;}
  3. bool Isroot(RG int x){return ch[fa[x]][1]!=x && ch[fa[x]][0]!=x; }
  4. bool Reverse(RG int x){swap(ch[x][1],ch[x][0]); rev[x] ^= 1;}
  5. void PushUp(RG int x){ ....... }
  6. void PushDown(RG int x)
  7. {if(rev[x])Reverse(ch[x][0]) , Reverse(ch[x][1]) , rev[x] ^= 1;}
  8. void Rot(RG int x){
  9. RG int y = fa[x] , z = fa[y] , c = Son(x);
  10. if(!Isroot(y))ch[z][Son(y)] = x; fa[x] = z;
  11. ch[y][c] = ch[x][!c]; fa[ch[y][c]] = y;
  12. ch[x][!c] = y; fa[y] = x; PushUp(y);
  13. }
  14. void Splay(RG int x){
  15. RG int top = 0; stk[++top] = x;
  16. for(RG int i = x; !Isroot(i) ; i = fa[i])stk[++top] = fa[i];
  17. while(top)PushDown(stk[top--]);
  18. for(RG int y = fa[x]; !Isroot(x) ; Rot(x) , y = fa[x])
  19. if(!Isroot(y))Son(x) ^ Son(y) ? Rot(x) : Rot(y);
  20. PushUp(x); return;
  21. }
  22. void Access(RG int x)
  23. { for(RG int y = 0; x; y = x,x = fa[x])Splay(x),ch[x][1] = y,PushUp(x); }
  24. void MakeRoot(RG int x){Access(x); Splay(x); Reverse(x);}
  25. int FindRoot(RG int x){Access(x); Splay(x); while(ch[x][0])x=ch[x][0]; return x;}
  26. void Split(RG int x,RG int y){MakeRoot(x); Access(y); Splay(y);}
  27. void Link(RG int x,RG int y){if((!x)||(!y))return; MakeRoot(x); fa[x] = y; }
  28. void Cut(RG int x,RG int y){if((!x)||(!y))return; Split(x,y); ch[y][0] = fa[x] = 0; PushUp(y);}
  29. }

B. LCT 维护 边权 信息

具体的实现方法有几种,这里只讲一种最简单、最常用的方法。

对于每一条边,新建一个点,然后连向对应的两个原树节点。

那么常见的维护方法是 代表点的节点不赋值,只有代表边的节点才赋值。

具体可以见这一道非常经典的题目: [NOI2014]魔法森林

伪代码为:

\(Link\)操作直接进行即可。

  1. IL void Link(Node u,Node v)
  2. NewNode(val_edge) ---(give)---> code_edge
  3. Link(u,code_edge); Link(v,code_edge);

\(Cut\)操作则需要找到这条边对应的两个节点,然后直接删除即可。

  1. IL void Cut(Edge e)
  2. Edge e ---(find)--> Node u1,u2 ;
  3. Delete(u1,code_e); Delete(u2,code_e);

这里注意 :

一定要找到对应的节点,而不是code_e的左右儿子!! ,不然保准你 WA 的怀疑人生。

然后一个非常经典的应用 就是 动态求图的割边(桥):

具体见这题:[AHOI2005]LANE 航线规划 ;;;;;;;;

C. LCT 维护 子树 信息

理论上来说\(LCT\)是不适合维护子树信息的,但总有一些毒瘤的出题人**..

给一篇写的非常详细的博客:http://blog.csdn.net/neither_nor/article/details/52979425

怎么搞呢?

先明确一下概念。

\(LCT\) 链剖后,一些路径变为了 单独的 一棵\(Splay\) ,

我们类似树链剖分,把节点划为实儿子、虚儿子。

实儿子就是在同一棵\(Splay\)里的那个儿子,其它则为虚儿子。

那么实儿子对应的子树就为实子树,虚儿子对应的子树就为虚子树。

.

其实我们原来的\(LCT\)就是维护了实子树对吧?

所以我们在维护子树信息时,只要再维护一下虚子树的信息即可。

以求子树大小为例。

我们假设\(sz[u]\)表示\(u\)的虚子树大小 , \(sum[u]\)表示整个子树大小,

那么显然,我们\(PushUp\)时,只需要更新\(sum\) , \(sz\)我们手动维护。

  1. IL void PushUp(RG int x){ sum[x] = sum[ch[x][0]]+sum[ch[x][1]]+sz[x]+1; }

那么考虑什么情况下会改变虚子树的大小,其实只有两个:

1. Access'(x) 操作

我们会把 \(x\) 原来的实儿子变为虚儿子,把另一个虚儿子变为实儿子。

我们对应的修改一下\(x\)的\(sz\)值,然后\(PushUp\)修改\(sum\)值即可。

  1. IL void Access(RG int x){
  2. for(RG int y = 0; x; y = x,x = fa[x]){
  3. Splay(x);
  4. sz[x] += sum[ch[x][1]] - sum[y];
  5. ch[x][1] = y; PushUp(x);
  6. }return;
  7. }

2. Link'(x,y) 操作

我们把\(x\)变为\(y\)的儿子。

那么显然\(x\)以及其所有祖先的\(sz\)值都会收到影响。

解决办法非常简单,把\(y\)也 \(MakeRoot\) 一下即可使得\(x\)只影响 \(y\) 。

注意一下由于我们修改了\(y\)的\(sz\)值,所以要\(PushUp\)一下\(y\)节点。

  1. IL void Link(RG int x,RG int y){
  2. if((!x)||(!y))return;
  3. MakeRoot(x); MakeRoot(y);
  4. fa[x] = y; sz[y] += sum[x]; PushUp(y);
  5. }

3.应用

应用一般就是求解会不断换根的子树信息问题。

最经典的一道题目是:「BJOI2014」大融合

D.LCT 维护图上信息

理论上来说\(LCT\)也是不适合维护图上信息的,但总有一些毒瘤的出题人..

图上信息的维护是不支持删边操作的。

其实图与树的差别就是可能有重边、环之类的。

那么既然我们只需要支持加边操作,那么只要用并查集维护缩点即可。

怎么维护呢? 这其实也是维护双联通分量的套路了。

.

开两个并查集:

(1)\(bzj[u][1]\),用于维护连通性

每次\(Link\)前,先查找一下两个点是否联通,如果不连通,则直接相连即可。

如果联通,则需要用到第二个并查集:

(2)\(bzj[u][2]\),用于维护双联通性

如果两个点已经双联通了,那么再加边其实对双联通性没有影响。

如果两个点的\(bzj2\)未相连,那么把两个的\(bzj2\)相连,然后缩点。

如果两个点的\(bzj2\)已相连,那么说明这两个点已经缩在一起了,所以无需连边。

.

那么此时\(bzj2\)其实就是每个点对应的缩点后的点\(id\)了。

然后是实现,实现的时候,想一想\(LCT\)的性质,我们可以发现:

只有向上跳父亲的时候,缩点才会对操作有影响

为了方便描述,定义一个\(F\)函数用于找到一个点\(u\)的对应\(id\)。

  1. int Find(RG int x,RG int od){return(bzj[x][od]==x)?x:Find(bzj[x][od],od);}
  2. int F(RG int x){return Find(x,2); }

然后正常的\(LCT\)操作都遵循上面那个结论,找父亲是\(Find\)一下即可。

以\(Splay\)为例(什么\(Access\)之类的都是一样的):

  1. IL void Splay(RG int x){
  2. RG int top = 0; stk[++top] = x;
  3. for(RG int i = x; !Isroot(i); i = F(fa[i]))stk[++top] = F(fa[i]);
  4. while(top)PushDown(stk[top--]);
  5. for(RG int y = F(fa[x]); !Isroot(x); Rot(x),y = F(fa[x]))
  6. if(!Isroot(y))Son(x) ^ Son(y) ? Rot(x) : Rot(y);
  7. PushUp(x); return;
  8. }

关键:\(Add(x,y)\) 操作

关键在于链接两个点时的操作,这时候是不能直接执行\(Link\)操作的。

先给代码:

  1. IL void Add(RG int u,RG int v){
  2. RG int x = F(u) , y = F(v);
  3. RG int f1 = Find(x,1) , f2 = Find(y,1);
  4. if(f1 != f2){
  5. bzj[f1][1] = f2;
  6. Link(x,y); return;
  7. }
  8. else{
  9. if(x == y)return;
  10. Split(x,y); Merge(y,y); //缩点
  11. ch[y][0] = ch[y][1] = 0; //!!!!!!!!
  12. }return;
  13. }

显然就是按照上面的并查集维护原则连边。

然后给一下缩点的代码:

  1. IL void Merge(RG int u,RG int rt){
  2. if(u ^ rt)
  3. sum[rt] += sum[u], bzj[F(u)][2] = rt, sum[u] = fig[u] = 0;
  4. if(ch[u][0])Merge(ch[u][0],rt);
  5. if(ch[u][1])Merge(ch[u][1],rt);
  6. }

其中\(sum\)为结点自身的信息(本身值),\(fig\)为子树信息(计算值)。

应用:

这个就依题目而定吧。

请记住一个最明显的特征:只支持加边,不支持删边!

\(LCT\)维护图上信息最经典的一题为:[BZOJ 2959]长跑 , 友情提示注意卡常。

有根LCT

自行yy,唯一一个需要注意的地方:

Cut(u,Fa) 的时候,必须把\(fa\)转到\(Splay\)顶端!(不能把\(u\)转到顶端),然后剪切。

  1. IL void Cut(int u , int Fa) {
  2. Access(u) ; Splay(Fa) ;
  3. ch[1][Fa] = fa[u] = 0 ; PushUp(Fa) ; return ;
  4. }

因为如果转\(u\)到顶端的话,剪切后整棵树的根就会产生变化,转移到\(u\)所属\(Splay\)中。

LCT 模板及套路总结的更多相关文章

  1. LCT模板

    之前一直用的LCT模板,因为其实个人对LCT和Splay不是很熟,所以用起来总觉得略略的坑爹,过了一段时间就忘了,但事实上很多裸的LCT要改的东西是不多的,所以今天写了些注释,以后可能套起模板来会得心 ...

  2. [洛谷P1501] [国家集训队]Tree II(LCT模板)

    传送门 这是一道LCT的板子题,说白了就是在LCT上支持线段树2的操作. 所以我只是来存一个板子,并不会讲什么(再说我也不会,只能误人子弟2333). 不过代码里的注释可以参考一下. Code #in ...

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

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

  4. BZOJ2002 & LCT模板(分块不会搞)

    题意: 看题. 某天,Lostmonkey发明了一种超级弹力装置,为了在他的绵羊朋友面前显摆,他邀请小绵羊一起玩个游戏.游戏一开始,Lostmonkey在地上沿 着一条直线摆上n个装置,每个装置设定初 ...

  5. bzoj2049-洞穴勘测(动态树lct模板题)

    Description 辉辉热衷于洞穴勘测.某天,他按照地图来到了一片被标记为JSZX的洞穴群地区.经过初步勘测,辉辉发现这片区域由n个洞穴(分别编号为1到n)以及若干通道组成,并且每条通道连接了恰好 ...

  6. Luogu 3690 LCT - 模板

    推荐几篇比较好的博客: FlashHu 的 讲解比较好 : 传送门 Candy 的 代码~ : 传送门 以及神犇Angel_Kitty的 学习笔记: 传送门 Code V 模板 #include< ...

  7. BZOJ 1180 / 2843 LCT模板题_双倍经验

    一大早上到机房想先拍一下模板,热热身. 结果....对照着染色敲的 LCT 竟然死活也调不过去(你说我抄都能抄错) 干脆自己重新敲了一遍,10min就敲完了....... 还是要相信自己 Code: ...

  8. BZOJ3282: Tree (LCT模板)

    Description 给定N个点以及每个点的权值,要你处理接下来的M个操作. 操作有4种.操作从0到3编号.点从1到N编号. 0:后接两个整数(x,y),代表询问从x到y的路径上的点的权值的xor和 ...

  9. LCT模板(学习笔记)(洛谷3690)(加边,删边,修改点权)

    最近学习了一波LCT qwq 强势安利Flashhu的博客!!!!! 真的特别详细(可惜我不会弄链接) 如果有想要学习\(LCT\)的同学,可以直接看他的博客 我这里就简单写一点自己的体会啊. \(L ...

随机推荐

  1. 代码从stepping stone搬移到内存

    为什么要搬移代码?如何搬移代码?arm启动流程回顾:2440:这里我们分析的是从nand flash 启动.2440的启动主要依赖于一个部件(SRAM),又名stepping stone.它的地址为0 ...

  2. Ubuntu系统下crontab的使用

    最近一个项目,需要用到一个定时任务,先说crontab的常用命令. crontab -u //设定某个用户的cron服务,一般root用户在执行这个命令的时候需要此参数 crontab -l //列出 ...

  3. D. Number of Parallelograms

    D. Number of Parallelograms 原题链接 time limit per test 4 seconds memory limit per test 256 megabytes Y ...

  4. postman 中调试接口的小记录

    1.form-data:  就是http请求中的multipart/form-data,它会将表单的数据处理为一条消息,以标签为单元,用分隔符分开.既可以上传键值对,也可以上传文件.当上传的字段是文件 ...

  5. C语言老司机学Python (三)

    条件语句: 注意1) condition后面的冒号 2) elif if condition_1: statement_block_1elif condition_2: statement_block ...

  6. SPI知识总结

    SPI知识总结 一.定义 SPI(Serial Peripheral Interface--串行外设接口)总线系统是一种同步串行外设接口,它可以使MCU与各种外围设备以串行方式进行通信以交换信息. 优 ...

  7. swift 学习之自动引用计数

    swift 学习之自动引用计数 学习和研究的主要是"实例对象和实例对象直接的相会强引用所产生的内从泄漏"和"使用闭包产生的强引用造成的内存泄漏" 注意:只有以引 ...

  8. sys.argv[]用法-转载

    sys.argv变量是一个字符串的列表.特别地,sys.argv包含了命令行参数 的列表,即使用命令行传递给你的程序的参数. 这里,当我们执行python using_sys.py we are ar ...

  9. 基于全志H3芯片的ARM开发环境搭建

    基于全志H3芯片的ARM开发环境搭建 最近买了个友善之臂的NanoPi M1板子,又在网上申请了个NanoPi NEO板子,这两个都是基于全志H3芯片的Crotex-A7四核ARM开发板,两个板子可以 ...

  10. 八爪鱼采集器︱加载更多、再显示20条图文教程(Xpatth、Ajax)

    每每以为攀得众山小,可.每每又切实来到起点,大牛们,缓缓脚步来俺笔记葩分享一下吧,please~ --------------------------- 由于代码布置采集器比较麻烦,又很早知道八爪鱼采 ...