2.29闲话 & solution 『再一分钟/再一秒钟/终会抵达幻想的尽头』
下面是学习笔记,但是似乎应该当做solution
想要省选的时候带点东西送给学长,但是看了看似乎不够。。。
。
发现还是对于LCT不够了解,所以又开了一篇博客
LCT
的概念
原本的树剖是对树进行剖分,剖分为重边和轻边
LCT则是对于树分为虚边和实边,特殊的,LCT可以没有实边(例:银河英雄传说v2)
单独被包含在一个实链里的点称作孤立点
在树剖中,我们使用线段树/树状数组来维护重链
在Link-Cut Tree
里我们使用一种更灵活地数据结构splay
来维护这些实链
splay
维护的是所有实边路径的中序遍历
每个实链都是一颗splay
每条实链之间都有一条虚边将其连接起来
x
的子节点其实是 x
在 splay
里的后继节点,而 x
的父节点其实是 x
在 splay
里的前驱节点
注意,这里 splay
本身也有父节点和子节点,但是 splay
里的父节点和子节点与原树的父节点和子节点没有任何关系
虚边用每个 splay
的根节点来维护
如下图,假设这里的右侧包含 x
和 r
节点的整颗 splay
的根节点为 r
,则虚边应该在splay
中记录为 r
的父节点,而非 x
的父节点
我们发现,对于虚边而言,子节点能够指向父节点,父节点却不知道子节点是谁
也就是说,路径用 splay
来维护,而路径与路径的关系用 splay
的根节点来维护
我们可以非常简单的把一条实边删掉,换成一条虚边,只要把父节点的后继修改即可
LCT
的基本操作
这里可以直接接上前面的学习笔记了
进阶一点的操作/配套题目
-
P1501 [国家集训队] \(\text{Tree II}\)
依然是链操作,但是有区间加法和区间乘法操作
这里参考了动态树大师
FlashHu
的题解-
区间加法
先用
spilt
操作把x
到y
的链分离出来然后整体直接加上
lazy
标记即可,并且在pushdown
操作时额外加入推平操作即可核心代码也很简单
inline void push_add(int x,int c){ (val[x]+=c*sz[x])%=51061; (v[x]+=c)%=51061; (lazy_add[x]]+=c)%=51061; }
在
pushdown
操作翻转前加入以下代码if(lazy_add[x]){ push_add(lc,lazy_add[x]), push_add(rc,lazy_add[x]), lazy_add[x]=0; }
-
区间乘法
也是先用
spilt
操作分离,然后挂lazy
标记,pushdown
操作加入乘法标记注意要先
pushdown
乘法的lazy
标记,再pushdown
加法的核心代码
inline void push_mul(int x,int c){ (val[x]*=c)%=51061; (v[x]*=c)%=51061; (lazy_mul[x]*=c)%=51061; (lazy_add[x]*=c)%=51061; }
在
pushdown
操作内pushdown
加法前加入以下代码if(lazy_mul[x]!=1){ push_mul(lc,lazy_mul[x]), push_mul(rc,lazy_mul[x]), lazy_mul[x]=1; }
那么就非常好搞了
核心代码都在上面了
-
-
维护两点是否连通,但是包含了断边操作和连边操作
这样普通的并查集就不好维护了,但是可以考虑使用
LCT
来维护维护方法就是直接对两点进行
Find
,如果Find
的结果相同那就是联通的-
核心代码
if(opt=="Q"){ FastO<<((Find(x)==Find(y))?"Yes":"No")<<endl; }
-
-
和上一题是双倍经验,基本区别不大
核心代码和上面一样