初学LCT小结
前言
笔者是一名石锤的菜鸡,到现在才学会一点基础的\(LCT\),此小结仅作为入门内容,大多借鉴自Flash学长的LCT总结,谨在此致谢。如果有后人翻到此博客建议转到Flash的总结。
但话说为什么要在csp/noip前学LCT啊
模板
#include<bits/stdc++.h>
#define ri register int
#define ll long long
#define ls t[x][0]
#define rs t[x][1]
#define I inline
using namespace std;
const int maxn = 3e5 + 10;
int s[maxn],v[maxn],fa[maxn],t[maxn][2],st[maxn],n,m;
bool r[maxn];
inline int rd(){
int res = 0,f = 0; char ch = getchar();
for(;!isdigit(ch);ch = getchar()) if(ch == '-') f = 1;
for(;isdigit(ch);ch = getchar()) res = (res<<3) + (res<<1) + ch - 48;
return f ? -res : res;
}
I bool nroot(int x){
return (t[fa[x]][0] == x) || (t[fa[x]][1] == x);
}
I void pushup(int x){
s[x] = s[ls] ^ s[rs] ^ v[x];
}
I void pushr(int x){
r[x] ^= 1; swap(ls,rs);
}
I void pushdown(int x){
if(r[x]){
r[x] = 0;
if(ls) pushr(ls);
if(rs) pushr(rs);
}
}
I void rotate(int x){
int y = fa[x],z = fa[y],k = (t[y][1] == x),w = t[x][!k];
if(nroot(y)) t[z][t[z][1] == y] = x; t[y][k] = w; t[x][!k] = y;
if(w) fa[w] = y; fa[x] = z; fa[y] = x;
pushup(y);
}
I void splay(int x){
int y = x,z = 0;
while(nroot(y)) st[++z] = y,y = fa[y];
st[++z] = y;
while(z) pushdown(st[z]),--z;
while(nroot(x)){
y = fa[x],z = fa[y];
if(nroot(y))
(t[z][1] == y) ^ (t[y][1] == x) ? rotate(x) : rotate(y);
rotate(x);
}
pushup(x);
}
I void access(int x){
int y = 0;
for(;x;y = x,x = fa[x])
splay(x),rs = y,pushup(x);
}
I void makeroot(int x){
access(x); splay(x); pushr(x);
}
I int findroot(int x){
access(x); splay(x);
while(ls) pushdown(ls),x = ls;
splay(x);
return x;
}
I void split(int x,int y){
makeroot(x);
access(y); splay(y);
}
I void link(int x,int y){
makeroot(x);
if(findroot(y) != x) fa[x] = y;
}
I void cut(int x,int y){
makeroot(x);
if(findroot(y) == x && t[x][1] == y && (!t[y][0])){
fa[y] = t[x][1] = 0;
pushup(x);
}
}
int main(){
n = rd(),m = rd();
for(ri i = 1;i <= n;++i) v[i] = rd();
while(m--){
int op = rd(),x = rd(),y = rd();
switch(op){
case 0 : split(x,y);printf("%d\n",s[y]);break;
case 1 : link(x,y);break;
case 2 : cut(x,y);break;
case 3 : splay(x);v[x] = y;
}
}
return 0;
}
\(LCT\)比较难的就是对\(pushup\)和\(pushdown\)的构造,其他部分都基本一样,顶多是维护虚子树的时候在\(access\)和\(link\)的时候要维护一下虚子树信息。
- 一点注释
- splay维护的是一条深度递增的链,splay内节点信息都是该节点所在splay中子树的信息和
- r[x]是类似于文艺平衡树的区间翻转标记,为的是将整条链翻转方向,个人写的是代表子树需要翻转且儿子已经翻转完
- LCT大概可以理解为维护的是由内向树组成的森林,要换根只要把新根和老根的链翻转即可
- split和findroot后都要splay来保证复杂度(不会证,记着吧)
- 不需要考虑断链时的维护(access内),因为splay内每个节点都维护好了子树代表的链的信息和,只要考虑合并两条链(pushup)即可
- 曾经\(RE\)的问题点:
- rotate内
t[z][t[z][1]==y] = x,写成 t[z][t[y][1]==x] = x - splay内忘记pushdown,手工栈的时候忘记
y = f[y] - main内op为char读入时没有
while(op != ...) op = getchar()
维护链信息
- P1501 [国家集训队]Tree II
路径加、乘、求和。
维护\(sum\)和\(tagmul\)、\(tagadd\),就是用splay来实现线段树2然后LCT套起来 - P3203 [HNOI2010]弹飞绵羊
对于弹飞建一个点,然后就是一棵树,要求动态删边、加边、求某个点到“弹飞”在树上的距离。把点权设为1求路径和即可。 - P2486 [SDOI2011]染色
确实是一道好题。路径颜色覆盖和路径颜色段个数。
对每个splay(每条链)维护路径内的颜色段个数和链两端的颜色,连两条链的时候把接口的贡献减一下即可。 - P4332 [SHOI2014]三叉神经树
真是一道好题啊,你一眼根本看不出是splay....貌似DDP更加直接
每次修改叶节点都会造成从底往上一段的点状态取反,比如\(1\rightarrow0\),导致上面一段都\(1\rightarrow0\),它们的\(sum\)(儿子状态和)都是\(2\rightarrow1\)。所以需要找到的就是从该叶节点到根 sum与会更改的sum不同的最深的点。维护sum不为1和sum不为2最深的点,\(access\)修改点然后判一下并区间修改sum即可。神奇的是每次区间+1/-1 都会造成不为1和不为2的点交换!每次pushtag的时候交换两个标记即可。

浙公网安备 33010602011771号