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;
}

连边操作,想想应该怎么搞。

很简单 (是真的简单) ,先判断一下\(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的主要内容,如果未能解决你的问题,请参考以下文章

Link-Cut-Tree 动态树算法

SPOJQTREE6(Link-Cut-Tree)

Link-cut-Tree

Link-Cut-Tree 题目总结

对于动态树 (Link-Cut-Tree, LCT) 的理解与总结

bzoj2049[Sdoi2008]Cave 洞穴勘测 link-cut-tree