LCT学习笔记
这个东西真是比较复杂,那就像吃 SAM 一样吃它吧!
我们一条链指向另外一条链的虚边代表的是两个 \(Splay\) 的根节点相连,所以LCT和原树就是一一对应的。
其实是每棵 \(Splay\) 的根节点的父亲节点指向原树中这条链的父亲节点
我们发现 \(Access()\) 其实很容易,只有如下四步操作:
把当前节点转到根。
把儿子换成之前的节点。
更新当前点的信息。
把当前点换成当前点的父亲,继续操作。
让我们来理解一下这是怎么不破坏性质的
和根联通很简单,因为我们一路上都把这个东西给连在一起了,也就是和父亲都在一条实链上,因为我们把父亲都转到根上,那么这个链下面的那一部分就变成虚链了,懂了!其实还有个性质就是我们把 \(p\) 旋转上来并不会把 \(p\) 的子树(原树中的子链)给旋上来
\(rotate\)
一样吧,不知道为什么题解要特意强调
\(makeroot\)
将 \(x\) 变成根。
如果我们指定了一个根,那么就变成了一个有根树,这个有向是体现在考虑我们换根相当于什么操作,其实就是将当前根到这个点的路径上所有点的边都反向了。考虑如何实现,我们把 \(p\) 旋上来,然后我们 \(p\) 到 \(x\) 这一条链就是整个\(Splay\)!所以把整棵树反转即可
\(link(x,y)\)
首先这两个东西不能发生在一个树内,所以我们如果连 \(x,y\),那么就将 \(y\) 设为 \(x\) 的父亲(其实都可以)。我们只需将 \(x\) 变成 \(root\),然后把 \(fa\) 设为 \(y\) 即可
\(split(x,y)\)
拉出一条 \(x-y\) 的路径,使其成为一颗平衡树。我们只要 \(makeroot(x)\),然后再 \(access(y)\) 即可,根据上面的性质这个是对的
\(find(x)\)
我们要找到 \(x\) 所在原树的根,那么我们一直 \(get(fa)\) 是不是就可以了。错!我们 \(access(x)\),然后 $$Splay\((x)\),那么根和 \(x\) 就在一个 \(Splay\) 里面了,所以我们直接在这个 \(Splay\) 里面找深度最小的点,也就是 \(Splay\) 里最左的点了,最后要 \(Splay\) 一下保证复杂度
\(cut(x,y)\)
我们直接判 \(x,y\) 是否存在。我们 \(split(x,y)\) ,然后如果是真的话,那么这个\(Splay\)应该只有两个点
时间复杂度,和全局平衡二叉树感觉差不多?但其实都没搞懂!
可以思考一下
注意,这个 \(access\) 是让\(x\)成为原树的根,而不是 \(LCT\) 中的根
可以发现中间有地方我们的东西是不对的,就是我们 \(link\) 和 \(cut\) 搞完之后。\(cut\) 是不会有问题的,有问题的只有 \(link\)。会不会是这样,\(link\)之后在一个比较下面的位置。哦,我们连的是虚边,所以不用 \(push\_ up\)
[P3203]
板子
但是有一些问题
我将答案看作 \(crt(x)\) 之后的答案是不对的,为什么,我将 \(x\) 变成原树的根,这个肯定不行
然后我最初的想法是 \(findrt\)。我们的 \(root\) 是原树中的 \(root\),为什么错了啊?因为我们 \(croot\) 过了,然后就错了,我再也不维护有根树了!
[P4332]
动态 \(DP\),好像并不是特别可以。这个状态不是特别好合并啊。其实如果我们从 \(30\) 变到 \(21\) 这种不会改变向上传颜色的是不用管的。但是如果是 \(21\) 变成 \(12\),那么我们就从下往上找到最长的 \(21\) 链,然后就可以了,那么我们。我们 \(split\) 出来从根到 \(x\) 的链,然后从下往上的维护即可了。
怎么维护呢?我们更新这个点,然后先更新这个点,然后就可以 \(access\) 一下,然后在这条链上找东西,然后看看根的答案。
树剖吧
还是练习 \(LCT\) 吧,我们要维护子树最
然后就做完了
访问 \(Splay\) 中的节点一定要先把这个点 \(Splay\) 上来啊!
[P2147]
板子
[P2542]
这个东西是和 \(tarjan\) 的一个结合。我们一个环其实就是一个点,那么这个东西怎么用 \(LCT\) 做呢?唉,此时要观察题目性质啊。删边不好做,我们改成加边,那么就是将这个点都缩到一个地方。对于一个点双,我们是不是可以使用并查集暴力缩点。也就是我们跳出已经联通的两个点的东西暴力维护即可。其实还有更优美的做法。我们建出 \(dfs\) 树,如果两个点不连通,那么就直接连起来,否则我们找出两个点的链每次两个点那么就相当于把这两个点中间的环锁掉,但至于之前已经缩过的,也自然都联通了。然后我们就是给边赋权值,\(LCT\) 给边赋权值怎么做呢?我们可以将边转化成点,或者我们加边,我们直接建出树,然后。对于边的,不好搞啊!就是怎么用点代表边呢?题解貌似是直接使用 \(n+m\) 个点来搞了,我觉得可以!应该不会 \(T\) 吧。
[P4234]
不难想到排序,然后从小向大把边扔进去,然后如果加进去发现已经联通了,那么就删掉这个环中最小的边,这样一定是最优的,代码貌似也不难写。我们发现还是要把边拆成点
[P4172]
这个东西,如果没有宣布报废这个东西,那么我们直接维护 \(kruskal\) 重构树即可,但本题加入了这个逆天东西,我们貌似就不能好好维护这个树了。然后,这个数据范围啊,非常可疑,可能突破口就是这里,\(1e3\) 个点,不会删超过 \(5e3\) 条?那么我们每次都重构一遍?这个复杂度不是特别可以接受。严旭之前的思路,我们维护出这个最小生成树,然后是不是求一个树上的一个路径的权值max,这个可以使用 \(LCT\) 维护,然后是不是 \(LCT\) 拆一下边就好了?如果把一个边拆掉了,这个比较难搞,我们正难则反,做完了!
[P2387]
要求一条路径,使得 \(min(a)+min(b)\) 最小,好神秘!数据范围也没有特别的。我们指定一开始的 \(A\),然后把所有 \(<=A\) 的边加进去,然后按 \(B\) 建最小生成树,这不就好了?我们二分 \(A\),这个 \(min(B)\) 一定是越来越小的,所以我们这个是三分?是不是会有平的一段,那么就不可以了。我们按 \(A\) 从小到大把边加进去,这个就可以使用 \(LCT\) 了,有点阴,只要判断 \(find(1)\) 是否等于 \(find(n)\) 即可,不一定要是一个生成树,好唐啊!
[P4219]
询问两个点的子树大小的乘积。这个其实用树剖是不是很快啊?可以吗?不太会。考虑怎么使用$ LCT$ 维护。考虑一个点的答案是什么,先把 \(x-y\) \(split\) 出来,我们只要求出 \(y\) 就行了,\(x\) 就是这整个 \(Splay\) 大小减一下。那么就是右子树加上右子树挂下去的 \(+1\)(本身)所以我们要维护一个点的虚子树的大小之和。考虑一个点的虚子树什么时候会改,当且仅当在 \(access\) 里会改,\(rotate\) 不会改的,然后 \(link\) 和 \(cut\) 会改,我们有 \(split\),那么就有 \(croot\) 和\(access\),然后还有一点函数,这个是不是挺好维护的?这个东西只能维护一些可以减的东西。那么我们就要维护的是子树内的所有点的大小之和那么就是子树的虚子树之和 \(+Splay\) 的大小。改一个元素的值一定要转到根啊!血的教训!不然 \(push_up\) 的是啥都不知道
[P4299]
动态维护树的重心?根据结论,我们每当加入一条边,树的重心。假了,还有一个就是所有子树大小的 \(max\) 最小的那个,但是这个子树大小啊,感觉很可维护啊!其实一点也不好维护。如果从链剖分的角度来看这个东西。我们对于每一个点,我们三种类型的子树,一种是往上的,一种是实链往下的子树。我们只要知道每个首都的变化就知道了我们编号的异或和。考虑一个编号加入带来的变化。这个东西因为是 \(max\),所以感觉没有前途啊。我们新的重心一定在两个重心的路径上,这应该是对的,我们
啊,有点被限制思路。不断合并,可以想到启发式合并,然后我们dfs就是不断把一个点连到,然后在中间不断判断子树大小。我们不断加点。
有点乱,但是感觉基本已经可以了。把 \(to\) 合并到 \(u\),我们,一定会将\(u\)所在的子树的重心 \((x)\),然后重心往 \(x\) 的 \(u\) 所在的子树进行移动,这个只要判断两个即可。寄,感觉时间复杂度假掉了啊!更新重心貌似要的时间很高?题解说是 \(log\),看一眼。哦哦哦哦哦哦哦!我们上面的不是重心一定在路径上,我们只要先 \(link\),然后把这条路径 \(Splay\) 出来,然后二分判断就行了。我们这条链上每个点都挂了一点 \(siz\),然后就是一个单增 \(+\) 一个单减函数的 \(min\),这个可以使用二分来解决,所以我们只要维护每个点虚子树的大小即可
这个还是很妙,完全把所有性质都用上了!
[P3703]
根据这个操作的特性,我们一个颜色的点只可能在一个联通块里,或者就是在一条链上,我们是否可以使用树剖。区间不同颜色的个数,在这题就变成了区间连续段个数,这显然是简单的。然后就是一个子树里的询问了,我们要找的就是子树内连续段最多。我们还是同样的希望跳重链来解决这个东西。我们的 \(f(x,g)\) 就是这个询问的答案,\(g\) 代表上一次的颜色。我们每个点记虚子树中连续段个数第一大的第二大的,这样如果 \(g\) 相同时我们只要两个比一下即可,然后往重链走,这不好维护啊。我们可不可以直接维护 \(x\) 到根的最长段。我们对于一个虚儿子,我们每次改一条链,链上貌似所有都要改,所以答案就是这条链到底,这个是好维护的,记得把链顶传上去。然后一个子树里也只要。我们线段树里面维护两个变量,一个是往重链的答案,还有一个是虚子树的 \(max\),那么每个点的答案就是线段树求一个 \(max\),然后。
重新搞一下。我们线段树维护两个东西,区间往虚子树的最大值(这个要记两个),还有区间连续段,那么我们这个点的答案就是这条链查到底,虚子树的最大值(两个),然后我们就挑出了前两个虚子树,然后我们直接判断就好了。\(x\) 到 \(y\) 的路径权值?直接维护即可。如何更新,我们首先一个一个跳,然后每次都更新链顶的一个,是不是没有可减性,这还得开个 \(map\) 来搞,反正还是 \(log^2\) 的,然后我们还是要单点更新线段树的。其他不用改变。区间连续段很经典,就不说了。查询 \(x\) 子树内,我们将 \(x\) 到根和下面合起来,\(x\) 的答案一直查到链底,找到最大的两个虚链,然后就可以了,貌似不是特别烦。
哈哈哈,全部假了,这是到根的
如何用 \(LCT\) 做?维护连续段好做,就是不断左右子树合并。我们修改一条链,然后全部都是 \(1\),然后 \(access\),是不是就是直接合并,重点是 \(x\) 子树内的查询。我们还是要维护这一坨东西是吧,和上面差不多吧我只能说,这个是 \(log^2\) 的,而且比较难写,因为我们虚实会变化,看看题解吧
我们维护区间到根的最大值。唉我们维护每个点到根的距离,这个就很好维护了,因为没有什么取最大值之类的操作。我们一条链上就全都是 \(1\),如果我们能维护这个点和父亲是否相同那么就很简单了。我们对于链顶单独求,然后对于链使用线段树。我们一个修改会将这条链的所有虚链的链顶变成。
还是用 \(LCT\) 吧,我们的思路是用 \(LCT\) 维护树上的联通块。每次就是把一个 \(access\) 一下就是这一个联通块了,然后呢?因为这个 \(LCT\) 的根不会变,所以我们就是 \(LCT\) 中所有点的。然后我们希望维护每个点到根的轻边条数。考虑在 \(access\) 里面改。发现我们 \(access\) 里面就是给一个子树 \(+1\) 和给一个子树集体 \(-1\)。然后我们是不是就做好了?
这题用了 \(LCT\) 一些很美妙的性质。就比如我们 \(LCT\) 其实反映了一点原树的形态的,就是我们 \(LCT\) 一颗 \(Splay\) 下面挂的所有点都在这个 \(Splay\) 最左节点的子树中。然后这题貌似乱 \(access\) 会挂掉,可能是。其实我没写假啊?\(rotate\) 挂了。
[P6292]
看看!
我们这个显然就是建一个 \(SAM\),然后维护 \(link\) 树上的 \(\sum siz[x]-siz[link[x]]\)。我们这个 \(SAM\) 是区间的所以还是比较诡异的。考虑一个区间的 \(SAM\) 长什么样,我们只需要统计子串内的 \(siz[x]-siz[link[x]]\),这个 \(x\) 需要 \(>=l\&\&<=r\),并且 \(link\) 要一直往前跳到 \(link\) 也属于。
不太对,首先我们的 \(siz\) 先赋好,哦,这个 \(siz\) 是 \(endpos\) 集合的大小哦,那么我们一个点的siz就是链求和,然后往前跳,我们不停往前跳。或者可以这样,我们先排个序,然后我们就一个一个加进去,然后是不是还得是从 \(link\) 减,显然做不下去了
考虑静态区间不同种类的数怎么求:
sol1:
在线
维护为 \(col[i]\) 在 \(i\) 之前的最后一次出现次数,然后就是区间 $ < l$ 的数的个数
sol2:
离线
这个办法看起来很蠢呢!
我们首先把询问离线,然后按右端点排序,然后我们想要维护每个位置是否对答案产生贡献。对于新拓展的i,我们在线段树上把上一次出现的位置答案 \(-1\),这个位置答案 \(+1\),因为有新的产生贡献,这个是对的因为我们询问排过序了。
考虑拓展到这题怎么做
考虑对于这道题怎么做。我们每次拓展一个东西的时候我们在这个地方找到最长的和前面本质相同的串,然后给前面那些 \(-1\)。欸!我们把这个东西放到 \(SAM\) 上,然后我们就有很美丽的性质了。我们每个点设定一个颜色,就是这个点最后出现的位置,那么我们就是给这条链都搞一遍颜色。然后我就会了。这个真的太优美了。我们一个节点的右端点就是一个东西,然后左端点就是一段区间,考虑怎么回答询问我们是不是。区间吗?我们给左端点加上答案,那么就是新加入一个点的话,就是直接 \(access\),然后把路上所有的。我们线段树里面存的是左端点是这个的有多少子串,那么我们就是一个区间减然后区间加就可以了?
也就是说,我们 \(SAM\) 里每个节点都有一个代表最右端的节点,然后本身还有一个 \(len\),然后这个节点在线段树上的贡献就是左端点区间 \(+1\),然后我们一个 \(access\) 会让链上所有的颜色段都减掉左端点贡献,然后右端点再来个区间加,然后后面就是一个区间查就做完了。
我们的 \(LCT\) 该怎么写呢?
我们感觉可以先把这个 \(SAM\) 建出来,然后 \(LCT\) 全都是空的节点,然后所有颜色都不同,然后每次 \(access\)