LCT学习笔记
板子
从问题看起:P3690
本题需要在一个动态图上实时查询各条链的状态(异或和)。因为两点联通时不连边,所以该图始终具有以下性质:
任意联通两点间有唯一路径。
因此该图是一个森林。
LCT 存储森林中的每条边,同时可以查询森林中任意一条链。当然也可以加边或删边。
思路:
对于每一棵子树,参考树链剖分,我们可以将树分为若干条链。那么每条链之间都可能有边相连。但边是动态的,这就导致链的结构不固定,无法用线段树维护。那么可以用什么来维护这么多结构不固定的链呢?
Tarjan 选择了结构同样不固定的 Splay。与树链剖分时相同,我们所维护的链中节点深度(指定某个节点为根的意义下)都是连续的。既然这样,节点深度就可以成为 Splay 的中序遍历。
也就是说我们为每一条链用一个 Splay 维护,其中序遍历即为根据从小到大遍历链上节点。
于是就有了实边与虚边。
现在主要来看在那几条定义的基础上,如何初步达到本题目标:访问任意一条链。
所有的虚边都是子能认父,因此一棵子树的根节点是该树内所有节点的祖先节点。又因为该树的任何一个非根节点都知道它的父节点,所以每一个节点都能通过不断访问父节点来跳到根部。
我们就可以在这样的过程中建一条链。这就是 access 函数:建立一个节点到它的根节点的链。
inline void access(int p){ int r = p; for (int c = 0; p; c = p, p = a[p].pa) splay(p), a[p].rs = c, pushup(p); splay(r); return; }
观察代码,发现在此过程中我们通过覆盖右 Splay 中的右子节点去除了该链上所有其他节点。这样不会影响图的结构,因为这仍然是一条虚边:被覆盖的子节点的父节点未变化。
目前为止,我们已经可以建立一条从任意节点到树根的链了。但如果我们想要的两个端点都不是树根怎么办?
把其中一个变成根不就行了:
inline void makeroot(int p){ access(p); revit(p); return; }
revit 函数为区间反转,作用是将 Splay 中某个节点的子树的中序遍历倒转。当我们建出由根到需求节点的链时,由于链中只有路径上的点,access 的节点必然在链上深度最大。此时反转整个 Splay,自然将该点变为了深度最小的节点——根节点。
现在就可以访问任意一条链了:
inline void split(int x, int y){ makeroot(x); access(y); return; }
至此,访问链的操作已经完成,还需要支持加边和删边。
我们需要一个判断两点是否联通的方法。很简单,只要看它们所在的树的根节点是否相同就行了。
如何找到当前节点的根?在 access 后,我们得到了一条从根到当前节点的链。这条链中序遍历的第一个节点显然就是树根。但是,记得处理懒标记。
inline int findroot(int p){ access(p); while (p && a[p].ls) pushdown(p), p = a[p].ls; splay(p); return p; }
根据边的定义,link 和 cut 如下:
inline void link(int x, int y){ makeroot(x); if (findroot(y) == x) return; a[x].pa = y; return; } inline void cut(int x, int y){ makeroot(x); if (findroot(y) != x || a[y].pa != x || a[y].ls) return; a[x].rs = a[y].pa = 0; pushup(x); return; }
总代码:
#include<bits/stdc++.h> #define inf 0x3f3f3f3f #define isdigit(x) ('0' <= x && x <= '9') using namespace std; const int MAXN = 100010; template<typename types> inline void read(types &x){ x = 0; int f = 1; char c = getchar(); while (!isdigit(c)){ if (c == '-') f = -1; c = getchar(); } while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f; return; } template<typename types> void write(types x){ if (x < 0) putchar('-'), x = - x; types k = x / 10; if (k) write(k); putchar(x - 10 * k + '0'); return; } int n, m; #define ls ch[0] #define rs ch[1] struct Node{ int ch[2], pa; int val, sum, rev; } a[MAXN]; stack<int> s; inline void pushup(int p){ a[p].sum = a[a[p].ls].sum ^ a[a[p].rs].sum ^ a[p].val; return; } inline bool check(int p){ return a[a[p].pa].rs == p; } inline void revit(int p){ a[p].rev ^= true; swap(a[p].ls, a[p].rs); return; } inline void pushdown(int p){ if (a[p].rev){ revit(a[p].ls), revit(a[p].rs); a[p].rev = false; } return; } inline bool isroot(int p){ return a[a[p].pa].ls != p && a[a[p].pa].rs != p; } inline void rotate(int x){ int y = a[x].pa, z = a[y].pa, dir = check(x), w = a[x].ch[!dir]; a[x].pa = z; if (!isroot(y)) a[z].ch[check(y)] = x; a[y].pa = x; a[x].ch[!dir] = y; a[w].pa = y; a[y].ch[dir] = w; pushup(y), pushup(x); return; } inline void splay(int p){ if (!p) return; int r = p; s.push(r); while (!isroot(r)) s.push(r = a[r].pa); while (!s.empty()) pushdown(s.top()), s.pop(); for (int pa = a[p].pa; pa = a[p].pa, !isroot(p); rotate(p)) if (!isroot(pa)) rotate(check(p) == check(pa) ? pa : p); return; } inline void access(int p){ int r = p; for (int c = 0; p; c = p, p = a[p].pa) splay(p), a[p].rs = c, pushup(p); splay(r); return; } inline void makeroot(int p){ access(p); revit(p); return; } inline int findroot(int p){ access(p); while (a[p].ls) p = a[p].ls; splay(p); return p; } inline void split(int x, int y){ makeroot(x); access(y); return; } inline void link(int x, int y){ makeroot(x); if (findroot(y) == x) return; a[x].pa = y; return; } inline void cut(int x, int y){ makeroot(x); if (findroot(y) != x || a[y].pa != x || a[y].ls) return; a[x].rs = a[y].pa = 0; pushup(x); return; } int main(){ read(n), read(m); for (int i = 1; i <= n; ++i) read(a[i].val); while (m--){ int op, x, y; read(op), read(x), read(y); if (op == 0){ split(x, y); write(a[y].sum), puts(""); } else if (op == 1) link(x, y); else if (op == 2) cut(x, y); else{ makeroot(x); a[x].val = y; pushup(x); } } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具