\(LCT\),全称 \(Link\) \(Cut\) \(Tree\),可以解决动态树问题。
如果一个点不是根,则考虑它既不是父亲的左儿子也不是父亲的右儿子。代码:
bool isroot(int x){
return tr[tr[x].p].s[0]!=x&&tr[tr[x].p].s[1]!=x;
}
假设 \(x\) 为 \(7\),\(y\) 为 \(3\),\(z\) 为 \(1\),也就是说要把 \(7\) 旋上去。考虑旋上去的话,就把父亲转到自己下面。于是先断开与父亲的边,连上与爷爷的边。
接下来发现父亲少了一个儿子,于是把自己的另外一边的儿子给父亲,同时把他连接自己的边断掉。
最后我们只需要链接 \(x\) 和 \(y\),只不过这次 \(x\) 是父亲。
代码:
void rotate(int x){
int y=tr[x].p,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;
pushup(y);
pushup(x);
}
先分类讨论一下,看 \(x,y,z\) 在不在一条直线上,可以看一下图:
不难发现,如果在一条直线上,则先转 \(y\),再转 \(x\);否则连着转两下 \(x\)。这样一直操作直到 \(x\) 被转到跟上。
但是在进行旋转之前,我们要先把转上去的路径做个 \(pushdown\)。代码:
void splay(int x){
int top=0,r=x;
stk[++top]=r;
while(!isroot(r))stk[++top]=r=tr[r].p;
while(top)pushdown(stk[top--]);
while(!isroot(x)){
int y=tr[x].p,z=tr[y].p;
if(!isroot(y)){
if((tr[y].s[1]==x)^(tr[z].s[1]==y))rotate(x);
else rotate(y);
}
rotate(x);
}
}