Link-Cut-Tree
Posted wr-eternity
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Link-Cut-Tree相关的知识,希望对你有一定的参考价值。
大体思路
可以看到,\(LCT\)就是用于解决这一类问题的,下面我们就来看一下它是怎么实现的。
我们知道有一种叫做树剖的东西,这玩意儿好像可以支持链上的一些操作。
我们还知道有一种叫做\(Splay\)的东西,这玩意儿貌似可以可以通过瞎搞完成很多动态的操作。
要不?让他们生个孩子?!
XD
嗯反正大致思路就是这样子的啦!
一些定义
Preferred Child: 重儿子,重儿子与父亲节点在同一棵Splay中,一个节点最多只能有一个重儿子。
Preferred Edge: 重边,连接父亲节点和重儿子的边。
Preferred Path : 重链,由重边及重边连接的节点构成的链。
Auxiliary Tree(辅助树):
由一条重链上的所有节点所构成的\(Splay\)称作这条链的辅助树。
每个点的键值为这个点的深度,即这棵\(Splay\)的中序遍历是这条链从链顶到链底的所有节点构成的序列
辅助树的根节点的父亲指向链顶的父亲节点,然而链顶的父亲节点的儿子并不指向辅助树的根节点
(也就是说父亲不认轻儿子只认重儿子,儿子都认父亲)
具体实现
假如我们现在有两种操作:
\(access(x)\)可以让你把\(x\)到当前实树的根的路径全部变成重链,也就是说,\(x\)到根路径上的所有点都会在一棵\(Auxiliary Tree\)中,而这可\(Auxiliary Tree\)上只有这些节点
\(makert(x)\)可以让你把\(x\)节点当做实树中的根(因为是树嘛,所以根是可以变的)
\(Query\)
有了这两种逆天操作,那么我们现在应该怎么维护题中的询问操作呢?
显然,我们只需要把\(x\),\(y\)路径上的那些点都放到一棵\(Auxiliary Tree\)即可。
那么我们就可以先把\(x\)作为实树的根,然后再把\(y\)和\(x\)之间的路径打通即可,这样这些点就全部在同一颗\(Auxiliary Tree\)上了。
如果要求值的话就\(Splay(x/y)\),然后你的\(Sum[x/y]\)就是答案了。
void query(int x,int y){
makert(x),access(y),splay(y);
}
\(Findrt\)
我们应该怎么寻找包含点\(x\)的实树的根呢?
我们只要先\(access(x)\)打通\(x\)到根的路径,再\(Splay(x)\),然后一直往左找就是根了。
因为根的深度是最小的嘛!
int findrt(int x){
access(x),splay(x);
while (ch[x][0]) pushdown(x),x=ch[x][0]; return x;
}
\(Link\)
连边操作,想想应该怎么搞。
很简单 (是真的简单) ,先判断一下\(x\),\(y\)两点是不是在一颗实树内,如果不在的话把他们连上就是了,当然,连的是虚边,儿子认爸爸,爸爸不认儿子的那种。
void link(int x,int y){
makert(x),fa[x]=y;
}
if (findrt(x)!=findrt(y)) link(x,y);
\(Cut\)
先判一判他们两个点之间是不是有边连接。
怎么判呢?
先把\(x\)变成根,再打通\(x\)和\(y\)之间的路,如果\(x\)和\(y\)之间直接就有连边的话,点\(y\)肯定就是在\(x\)的下面一层.
那么把\(y点\)\(Splay\)到根之后\(x\)肯定是他的左儿子,而且点\(x\)是没有右儿子的,因为\(x\)的重儿子只有一个且只能是\(y\),所以不会再出现深度比\(x\)大,比\(y\)小的点。
void cut(int x,int y){
makert(x),access(y),splay(y);
if (ch[y][0]!=x&&!ch[ch[y][0]][1]) return;
ch[y][0]=fa[x]=0,update(y);
}
\(Access\)
相信把上面的操作都讲了一圈以后,大家对于\(access\)操作的理解都加深了吧,那么接下来\(access\)的实现方法就很简单了。
在\(access\)操作中,我们要做到两点:
断掉父亲节点原来的重链
把父亲节点和它的辅助树合并
我们只要一直重复这两步,一直到父亲为实树的根为止(具体表现为\(fa[x]\)为\(0\))。
void access(int x){
for (int y=0;x;y=x,x=fa[x]) splay(x),ch[x][1]=y,update(x);
}
\(Makert\)
很简单,把点\(x\)到根的路径打通,然后把\(x\)点\(Splay\)到根即可。
注意,因为此时的根节点发生变换,所以所有节点的深度都会翻转,要给\(x\)打一个翻转标记。
void makert(int x){
access(x),splay(x),rever(x);
}
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=300005;
int fa[N],ch[N][2],val[N],w[N],rev[N];
int n,q,opt,x,y;
int where(int x){return ch[fa[x]][1]==x;}
bool isrt(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}
void rever(int x){rev[x]^=1,swap(ch[x][0],ch[x][1]);}
void update(int x){val[x]=val[ch[x][0]]^val[ch[x][1]]^w[x];}
void pushdown(int x){
if (rev[x]){
if (ch[x][0]) rever(ch[x][0]);
if (ch[x][1]) rever(ch[x][1]);
rev[x]=0;
}
}
void Allpushdown(int x){
if (!isrt(x)) Allpushdown(fa[x]);
pushdown(x);
}
void rotate(int x){
int par=fa[x],son=x,c=where(x);
if (!isrt(par)) ch[fa[par]][where(par)]=son;
fa[son]=fa[par];
ch[par][c]=ch[son][c^1];
fa[ch[par][c]]=par;
ch[son][c^1]=par;
fa[par]=son;
update(par),update(son);
}
void splay(int x){
Allpushdown(x);
for (;!isrt(x);rotate(x))
if (!isrt(fa[x])) rotate(where(fa[x])==where(x)?fa[x]:x);
}
void access(int x){
for (int y=0;x;y=x,x=fa[x]) splay(x),ch[x][1]=y,update(x);
}
void makert(int x){
access(x),splay(x),rever(x);
}
int findrt(int x){
access(x),splay(x);
while (ch[x][0]) pushdown(x),x=ch[x][0]; return x;
}
void link(int x,int y){
makert(x),fa[x]=y;
}
void cut(int x,int y){
makert(x),access(y),splay(y);
if (ch[y][0]!=x&&!ch[ch[y][0]][1]) return;
ch[y][0]=fa[x]=0,update(y);
}
void query(int x,int y){
makert(x),access(y),splay(y);
}
int main(){
scanf("%d%d",&n,&q);
for (int i=1;i<=n;i++) scanf("%d",&w[i]);
while (q--){
scanf("%d%d%d",&opt,&x,&y);
if (opt==0) query(x,y),printf("%d\n",val[y]);
if (opt==1) if (findrt(x)!=findrt(y)) link(x,y);
if (opt==2) cut(x,y);
if (opt==3) w[x]=y,splay(x);
}
return 0;
}
以上是关于Link-Cut-Tree的主要内容,如果未能解决你的问题,请参考以下文章