这是之前的总结,就是讲了一下基础。

基础题

LCT 适用于维护动态的题目,特别是有删边和加边操作的题目,如果题目明确说明需要删边和加边,那大概率就差把“LCT”写在题面里面了。


在写代码上有一个需要注意的地方就是 LCT 不是 leefy 的,所以 pushup 需要把当前节点的贡献也算上。

P3203 [HNOI2010] 弹飞绵羊

P3203 [HNOI2010] 弹飞绵羊。

就是维护这个点到下一个能跳到点连边,如果跳出去连 n+1n+1,最后看这个点到 n+1n+1 的距离。

P1501 [国家集训队] Tree II

P1501 [国家集训队] Tree II。

这道题就是模板的进阶,像线段树那样写 Tag 就好了。

P2173 [ZJOI2012] 网络

P2173 [ZJOI2012] 网络。

才开始读错题以为不一定是树,想着线段树分治+LCT。。。

对每个颜色开一个 LCT 就行了。最难受的是特判,颜色相同就不需要操作,直接输出 success。

维护序列关系

这一部分就跟线段树很想了,就是变了一下维护它的数据结构。写法反正我一般就是类似地使用结构体,就是需要注意很多细节,还有和模板的差异。

U684923 树上最长顺子

U684923 树上最长顺子。

lsy 学长自己随手出的例题,放这里刚刚好。

像线段树那样,维护各种能表示区间的信息。

注意:初始化 c.ld = 0;c.rd = 0; 因为 cc 有可能只有一个节点,后面赋值赋不到。

思路

需要维护的信息:区间长度,区间左右端点权值,最长前缀顺子长度(递增/递减),最长后缀顺子长度,区间内最长顺子长度。

于是我们可以根据 ls−x−rsls-x-rs 合并出 xx 此时的状态和答案。

注意 pushdown 等标记的传送,还有反转操作的。

维护 MST

LCT 可以根据最小生成树的性质,也就是 Kruskal 证明的一些结论,进行最小生成树的维护。比如说我们加入一条边,需要找到这条边两个端点原路径上的边权最小值,看看能不能替换。如果能,就进行 LCT 的删边/加边操作。

P2387 [NOI2014] 魔法森林

P2387 [NOI2014] 魔法森林。

这道题边权有两个维度,我们固定一维,然后计算另一维的最小值。我们枚举选取 aa,然后一点点把在范围内的边加进去,处理 bb 的 MST。

P4172 [WC2006] 水管局长

P4172 [WC2006] 水管局长。

这道题就是维护 MST 的模板。

由于最小生成树删边不好做,所以我们加边倒序做。

然后我们发现,维护 MST 可以插入一条边,然后会形成一个环,删掉环上的最长的边即可。

结合 Kruskal 可以证明,因为当前加边如果不用,那就一定在 Kruskal 过程中在这些边后加入,所以这条边不加进去了。

然后维护边权有两种思路,一是转化成点权,子节点储存边权,二是中间新建一个节点。

维护子树信息

LCT 模板是维护链上信息的,但是它也可以维护子树信息。想想 Splay,我们 pushup 的时候可以把整个子树的信息 push 上去。

很多时候,我们需要单独开一个数组去记录虚儿子的信息。

P4219 [BJOI2014] 大融合

P4219 [BJOI2014] 大融合。

这道题中我们需要维护子树大小。

然而,LCT 只是维护实链信息的,所以我们需单独开一个数组记录虚儿子的信息。

这个用 sisi 记录,然后我们发现我们需要在 access 时,也就是改变虚实节点的时候更新。

pushup 时也不要忘了。

思路

每次询问,我们可以先断掉这条边,然后询问这两条边上的子树大小。

代码的主要修改点就是 access 还有 pushup 的逻辑。

C++
void pushup(int x){
    sum[x]=sum[ls]+sum[rs]+si[x]+1;
}

void access(int x){
    for(int s=0;x;s=x,x=fa[x]){
        splay(x);
        si[x]+=sum[rs];
        rs=s;
        si[x]-=sum[rs];
        pushup(x);
    }
}

主函数中:

C++
split(x,y);
            ch[y][0]=fa[x]=0;
            pushup(y);
            makeroot(x);
            makeroot(y);
            long long ans=(long long)sum[x]*(long long)sum[y];
            cout<<ans<<endl;
            fa[x]=y;
            si[y]+=sum[x];
            pushup(y);

维护颜色数

很巧妙,每一个颜色用一个 splay。

P3703 [SDOI2017] 树点涂色。

这里就不详细讲了。

P5526 [Ynoi2012] 惊惶的 SCOI2016

P5526 [Ynoi2012] 惊惶的 SCOI2016。
CF1172E Nauuo and ODT。

给你一棵 nn 个节点的树,每个点有个颜色,有 mm 次修改,每次修改需要输出树上所有有向简单路径的颜色数的和。

首先,我们需要求对于每个颜色有多少条路径包含了。

神奇正难则反:求不包含这个颜色的路径数。然后此时的答案就是选取不包含这个颜色的节点,所有连通块大小的平方和。

然后就想办法用 LCT 维护颜色数。

思路

最简单的想法是对于每一个颜色维护 LCT,但是显然不行。

我们发现对于一种颜色,其余颜色怎么修改也影响不到,所以这是相对独立的。

又因为这道题没有强制在线,是每次询问后输出,我们又想到了类似差分的转为离线方法。

我们只需要离线下来处理每一个颜色即可。然后这就变成了一个双色问题,题解中说了一道 Qtree6,就是双色问题的基础。

至于维护什么?我们需要维护子树信息,我们维护一个点所有虚儿子的子树大小的平方和就行了。

维护 anxan_x 表示如果把 xx 看作一个分割点,将它下方的所有虚边全部切断,那么得到的各个连通块(由实链连接的整体算一个块)的大小平方之和。然后连通块最上面的点是白点(也就是不能算进连通块的点)。

P7735 [NOI2021] 轻重边

P7735 [NOI2021] 轻重边。

直接用 LCT 的实/虚边模拟即可。不想写了,因为这道题写树剖更快。

树剖的话用一个技巧:染色。因为链上所有的节点都需要先变成轻边,所以只需要把这一段染成跟所有节点都不同的颜色即可,重边即颜色相同的一段。

配合线段树分治

这个...就有点恶心了吧,反正我还没学会。