建议在右侧的设置按钮中打开阅读器模式阅读
虽然基于 Splay,但是并没有 Splay 难调
LCT 简介
LCT 是一种解决动态树问题的算法,就是在树链剖分的基础上可以断开或连接边(当然保证还是一棵树)。
那么对于这种问题,自然就不能用重链剖分来解决,这时便需要一个新的组合——实链剖分+Splay
模板题看这里
实链剖分
对于一个点连向它所有儿子的边,我们自己选择一条边进行剖分,我们称被选择的边为实边,其他边则为虚边。对于实边,我们称它所连接的儿子为实儿子。对于一条由实边组成的链,我们同样称之为实链 —— Oi-Wiki
Splay
先放两篇博客,毕竟这种被 FHQ 吊打的数据结构不太想写,也只需要会一点点就好了
「学习笔记」平衡树——splay 一
「学习笔记」平衡树——splay 二
算法讲解
思路
对于一棵树,我们将它剖成若干条实链:

如图所示,整棵树被剖分成{1,4,7},{2,6,10},{3},{5},{8},{9}这些实链。
所以我们可以以深度为关键字,对每条连用一棵 Splay 来维护。
但是我们如何对于原树进行维护?可以对每一棵 Splay 的链顶的父亲节点进行记录,但是从原树上的父亲不会向它连边
具体实现可以将链顶的 fa
值设为链头在原树上的父节点
然后就可以用一棵新的树,也就是辅助树替代掉原树:

这就是 LCT 大致的思路。
实现
先鸽一下……
本人用的结构体写法……
Splay 部分
结构体:
1 2 3 4 5
| struct node { int val, xval; int ch[2], fa, lazy_rev; } tr[N];
|
上传与下放:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void pushup(int x) { tr[x].xval = tr[tr[x].ch[0]].xval ^ tr[tr[x].ch[1]].xval ^ tr[x].val; } void pushdown(int x) { if(tr[x].lazy_rev) { int p; if(p = tr[x].ch[0]) swap(tr[p].ch[0], tr[p].ch[1]), tr[p].lazy_rev ^= 1; if(p = tr[x].ch[1]) swap(tr[p].ch[0], tr[p].ch[1]), tr[p].lazy_rev ^= 1; } tr[x].lazy_rev = 0; }
|
旋转:
1 2 3 4 5 6 7 8
| void rotate(int x) { int y = tr[x].fa, z = tr[y].fa, k = getch(x); if(!isroot(y)) tr[z].ch[getch(y)] = x; tr[x].fa = z; tr[y].ch[k] = tr[x].ch[k ^ 1]; tr[tr[y].ch[k]].fa = y; tr[x].ch[k ^ 1] = y, tr[y].fa = x; pushup(y); }
|
伸展:
1 2 3 4 5 6 7 8 9 10 11 12
| void splay(int x) { update(x); while(!isroot(x)) { if(!isroot(tr[x].fa)) rotate(getch(x) ^ getch(tr[x].fa) ? x : tr[x].fa); rotate(x); } pushup(x); }
|
LCT 部分
一共十个函数……
判断是哪棵子树:
1 2 3 4
| int getch(int x) { return tr[tr[x].fa].ch[1] == x; }
|
判断是否为根:
1 2 3 4 5
| bool isroot(int x) { return tr[tr[x].fa].ch[0] != x && tr[tr[x].fa]. ch[1] != x; }
|
操作从根到x上的所有子节点的懒标记:
1 2 3 4 5 6
| void update(int x) { if(!isroot(x)) update(tr[x].fa); pushdown(x); }
|
下面是整个 LCT 中最核心的操作——将 x 到根打通为实链(仅是从 x 到根路径上的点)
对于在 x 点时,显然不会有实链,所以将 x 伸展到根后直接断开 x 与下面的连接
在其他点时,伸展到根后两部分:左儿子——在当前链上深度小于当前点的部分,右儿子——在当前链上深度大于当前点的部分。那么因为要把当前点与上一次的连接,就断开与右儿子的连接,将上一次操作的点连在右儿子上。
一直这样操作直到连到原树树根。
1 2 3 4 5 6 7 8 9 10
| void access(int x) { int p = 0; for(; x; p = x, x = tr[x].fa) { splay(x); tr[x].ch[1] = p; pushup(x); } }
|
将x换为原树的根:
1 2 3 4 5 6 7
| void makeroot(int x) { access(x); splay(x); swap(tr[x].ch[0], tr[x].ch[1]); tr[x].lazy_rev ^= 1; }
|
将x~y打通成实链:
1 2 3 4 5 6 7
| int split(int x, int y) { makeroot(x); access(y); splay(y); return y; }
|
找x所在原树的根:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| int find(int x) { access(x); splay(x); pushdown(x); while(tr[x].ch[0]) { x = tr[x].ch[0]; pushdown(x); } splay(x); return x; }
|
连接x,y点:
1 2 3 4 5
| void link(int x, int y) { makeroot(x); if(find(y) != x) tr[x].fa = y; }
|
断开x, y:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void cut(int x, int y) { split(x, y); if(tr[y].ch[0] == x && tr[x].ch[1] == 0)、
{ tr[y].ch[0] = tr[x].fa = 0; pushup(y); } }
|
修改x的点权:
1 2 3 4 5 6
| void fix(int x, int v) { splay(x); tr[x].val = v; pushup(x); }
|
完整代码
70行极限压行,真男人才懂得压行的魅力~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
|
#include <bits/stdc++.h> #define int long long #define FH signed using namespace std; const int N = 1e5 + 10; struct node {int val, xval, ch[2], fa, lazy_rev;} tr[N]; int getch(int x) {return tr[tr[x].fa].ch[1] == x;} void pushup(int x) {tr[x].xval = tr[tr[x].ch[0]].xval ^ tr[tr[x].ch[1]].xval ^ tr[x].val;} void pushdown(int x) { if(tr[x].lazy_rev) { int p; if(p = tr[x].ch[0]) swap(tr[p].ch[0], tr[p].ch[1]), tr[p].lazy_rev ^= 1; if(p = tr[x].ch[1]) swap(tr[p].ch[0], tr[p].ch[1]), tr[p].lazy_rev ^= 1; }tr[x].lazy_rev = 0; } bool isroot(int x) {return tr[tr[x].fa].ch[0] != x && tr[tr[x].fa]. ch[1] != x;} void update(int x) { if(!isroot(x)) update(tr[x].fa); pushdown(x);} void rotate(int x) { int y = tr[x].fa, z = tr[y].fa, k = getch(x); if(!isroot(y)) tr[z].ch[getch(y)] = x; tr[x].fa = z; tr[y].ch[k] = tr[x].ch[k ^ 1]; tr[tr[y].ch[k]].fa = y; tr[x].ch[k ^ 1] = y, tr[y].fa = x; pushup(y); } void splay(int x) { update(x); while(!isroot(x)) {if(!isroot(tr[x].fa)) rotate(getch(x) ^ getch(tr[x].fa) ? x : tr[x].fa); rotate(x);} pushup(x); } void access(int x) { for(int p = 0; x; p = x, x = tr[x].fa) splay(x), tr[x].ch[1] = p, pushup(x); } void makeroot(int x) {access(x), splay(x), swap(tr[x].ch[0], tr[x].ch[1]), tr[x].lazy_rev ^= 1;} int split(int x, int y) {makeroot(x), access(y) , splay(y); return y;} int find(int x) { access(x), splay(x), pushdown(x); while(tr[x].ch[0]) x = tr[x].ch[0], pushdown(x); splay(x); return x; } void link(int x, int y) {makeroot(x); if(find(y) != x) tr[x].fa = y;} void cut(int x, int y) {split(x, y); if(tr[y].ch[0] == x && tr[x].ch[1] == 0) tr[y].ch[0] = tr[x].fa = 0, pushup(y);} void fix(int x, int v) {splay(x), tr[x].val = v, pushup(x);}
FH main() { int n, m; cin >> n >> m; for(int i = 1; i <= n; i ++) cin >> tr[i].val; for(int i = 1; i <= m; i ++) { int opt, x, y; cin >> opt >> x >> y; if(opt == 0) cout << tr[split(x, y)].xval << "\n"; if(opt == 1) link(x, y); if(opt == 2) cut(x, y); if(opt == 3) fix(x, y); } return 0; }
|
说句闲话,这是我生日过的紫题呢~~~
完结撒花~~~