基础篇: 简介概念和模板
进阶篇: 只有题目
模板分析: 来自 ac wing
刷题记录: pt 23.2
问题描述
- 查询、修改链上的信息(最值,总和等)
- 随意指定原树的根(即换根)
- 动态连边、删边
- 合并两棵树、分离一棵树
- 动态维护连通性
细化步骤
重点提醒:
1,子节点不会跳出当前节点所在的实链:
虚链链接的时候,父亲不知道儿子是谁,但是儿子知道父亲
2,子节点发生变动的节点需要up,自顶向下递归子节点的时候需要down
3,Up 或者 Down一个不能少, LCT 由于特别灵活
的原因,少 down 或 up 一次就可能
把修改改到不该改的点上!
4,LCT 的 Rotate 和 Splay 的不太一样,if (z) 一定要放在前面。
5,LCT 的 Splay 操作就是旋转到根
,没有旋转到谁儿子的操作,因为不需要。
splay部分
1, spin
只须在对z的儿子进行操作时注意一点:
y若是实链顶部(root),z的子节点不能发生改变
void spin(int x)
{
int y= tr[x].p;
int z= tr[y].p;
int k= tr[y].s[1]==x;
if(!isroot(y))tr[z].s[tr[z].s[1]==y]=x;
tr[x].p=z;
tr[y].s[k]=tr[x].s[k^1]; tr[tr[x].s[k^1]].p=y;
tr[x].s[k^1]=y; tr[y].p=x;
up(y); up(x);
}
2, splay
以往 splay 找某个节点的时候,都是从根节点往下找(按照 BST 的性质)
但是在 LCT 中,splay 充当的是辅助树
的角色,我们获得 splay 中的节点是通过原树中对应节点的编号
换而言之,我们是直接获得
splay 中的某个节点,而不是自上而下递归找到的
所以,在做 splay 转到根节点的旋转操作时,我们需要先 自上而下
把 懒标记
下传
void splay (int x)
{
int top = 0;
int r = x;
stk[++top]=x;
while (!isroot(r))stk[++top]=r=tr[r].p;
while (top) down(stk[top--]);
while (!isroot(x))
{
int y= tr[x].p;
int z= tr[y].p;
if(!isroot(y))
{
if((tr[y].s[1]==x) ^ (tr[z].s[1]==y))spin(x);
else spin(y);
}
spin(x);
}
}
LCT 部分
子节点发生变动的节点需要up
自顶向下递归子节点的时候需要down
1, access 定位
将x到根(实际的树)的路径修改为实边,并把x转到根(保证splay复杂度)
void access(int x)
{
int z =x;
for (int y= 0; x; y=x,x=tr[x].p)
{
splay(x);
tr[x].s[1]=y;
up(x); 改接树上结构需要up
}
splay(z); access自带把x转上辅助树的树根
}
2, cut 断边
cut的条件:
1,y 所在的原树中的根是否是 x
2,y 的父节点是否是根 x
3,y 是否有左孩子(中序遍历紧挨在 x 的后面)
void cut(int x,int y)
{
make_root(x);
if(find_root(y)==x && tr[y].p==x && !tr[y].s[0])
{
tr[x].s[1]=tr[y].p=0;
up(x);
}
}
3, 其他操作
int find_root(int x)/// 辅助树的root
{
access(x);
while (tr[x].s[0])
{
down(x); ///忘记2
x=tr[x].s[0];
}
splay(x);
return x;
}
void make_root(int x)/// 把x做成原树的根节点,需要反转整个中序遍历
{
access(x);
push_rev(x);///q1
}
void sign(int x,int y) ///把x到y的路径标记为实边
{
make_root(x);
access(y);
}
void link(int x,int y)
{
make_root(x);
if(find_root(y)!=x )tr[x].p=y;
}