LCT学习笔记

Orz 大神 PoPoQQQ的ppt
代码主要学习于Saramanda

开篇

LCT=树链剖分+splay 能解决动态的加边和动态的删边的问题,并且可以维护一条路径上的信息。
LCT分为原树和辅助树,但维护的不是原树而是辅助树
辅助树的每一个节点只记父亲不计儿子 但是每一个节点又有着自己唯一的重儿子,且和重儿子由重边相连,重边构成重链。每一个重链都有splay储存。且splay是把深度作为权值。所以说splay的中序遍历就是这一个重链从上到下的序列。

可能不太清晰,但记住每一个splay代表一条链,且splay符合原树中的权值大小关系。

初始化

struct node
{
    int fa,ch[2]; bool reverse,is_root;//父亲 两个儿子 反转标记 是否是splay的根节点。
}T[maxn];

操作

access

LCT中的核心操作,其余的操作基本基于该操作。
access操作是将当前节点x向根节点开通一条重链

方式

将当前的点x splay到该链的根部。x再转到父亲上不断进行该操作直到树根。
void access(int x)
{
    int y=0;//y记录上一次操作的点初始化为0
    do
    {
        splay(x);
        T[T[x].ch[1]].is_root=true;//因为x以上的节点深度都小于x,所以x的右儿子一定是x以下的节点
        T[T[x].ch[1]=y].is_root=false;//上一次的y深度大于x,且连通x与y
        //如果维护路径信息 update(x);
        x=T[y=x].fa;//让x向上走。
    }while(x);
}

mroot

将节点x移到树的树根。

方式

很明显可以将当前的x连通到树根处。 再splay x 到树根。但是因为x是最下部的节点及深度最大。

所以当前的辅助树并不满足x深度最小。 显然的是如果将辅助树倒转,就符合x深度最小。

void mroot(int x)
{
    access(x); splay(x); pushreverse(x);
}

标记

如果说要一个个的把每一个节点都反转的话。时间复杂度太高,不能接受。所以可以打一个标记,代表着以该节点为根的子树(不包括根)需要倒转。

倒转

void pushreverse(int x)
{
    if(!x) return;
    swap(T[x].ch[0],T[x].ch[1]);
    T[x].reverse^=1;
}

但是如果有了当前的标记的话,在splay的时候又需要把x一直到根的标记 从上向下降下来。
(因为现在的树是倒转的,所以从x向上都需要完成操作,不能有标记)。

void pushdown(int x)
{
    if(T[x].reverse)
    {
        pushreverse(T[x].ch[0]); pushreverse(T[x].ch[1]);//有标记就降给两个儿子
        T[x].reverse=false;
    }
}
void push(int x)
{
    if(!T[x].is_root) push(T[x].fa);//向上找
    pushdown(x);//每一个都处理
}

splay

直接上代码

void rotate(int x)
{
    int son=getson(x),fa=T[x].fa; int g=T[fa].fa;
    T[fa].ch[son]=T[x].ch[son^1]; T[fa].fa=x; T[x].fa=g;
    if(T[x].ch[son^1]) T[T[x].ch[son^1]].fa=fa; 
    T[x].ch[son^1]=fa;
    if(!T[fa].is_root) T[g].ch[T[g].ch[1]==fa]=x;
    else T[x].is_root=true,T[fa].is_root=false;
    //同理 需要维护的话 update(fa); update(x);
}
void splay(int x)
{
    push(x);//降掉标记。
    for(int fa; !T[x].is_root; rotate(x))
    {
        if(!T[fa=T[x].fa].is_root) rotate(getson(x)==getson(fa)? fa: x);
    }
}

连接x,y

方式

将x移到树根 并且将x的父亲赋成y (这是一条轻边)

void link(int x,int y)
{
    mroot(x); T[x].fa=y;
}

cut

切掉 x与y之间的边。

方式

将x移到树根,再连通y与x之间的路径,再将y splay到树根,y的左儿子就是x,再切掉边。
证明是反证法 详情请看popoqqq的ppt。

void cut(int x,int y)
{
    mroot(x); access(y); splay(y);
    T[x].is_root=true; T[x].fa=T[y].ch[0]=0;
}

较完整版代码

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=10000000+10;
struct node
{
    int fa,ch[2];
    bool reverse,is_root;
}T[maxn];
int getson(int x);
void pushreverse(int x);
void pushdown(int x);
void push(int x);
void rotate(int x);
void splay(int x);
void access(int x);
void mroot(int x);
void link(int u,int v);
void cut(int u,int v);
int main()
{
    freopen("test.in","r",stdin);
    freopen("test.out","w",stdout);


    return 0;
}
int getson(int x)
{
    return x==T[T[x].fa].ch[1];
}
void pushreverse(int x)
{
    if(!x) return;
    swap(T[x].ch[0],T[x].ch[1]);
    T[x].reverse^=1;
}
void pushdown(int x)
{
    if(T[x].reverse)
    {
        pushreverse(T[x].ch[0]); pushreverse(T[x].ch[1]); T[x].reverse=false;
    }
}
void push(int x)
{
    if(!T[x].is_root) push(T[x].fa);
    pushdown(x);
}
void rotate(int x)
{
    int k=getson(x),fa=T[x].fa;int g=T[fa].fa;
    T[fa].ch[k]=T[x].ch[k^1];
    if(T[x].ch[k^1]) T[T[x].ch[k^1]].fa=fa;
    T[x].ch[k^1]=fa; T[fa].fa=x; T[x].fa=g;
    if(!T[fa].is_root) T[g].ch[fa==T[g].ch[1]]=x;
    else T[x].is_root=true,T[fa].is_root=false;
}
void splay(int x)
{
    push(x);
    for(int fa; !T[x].is_root; rotate(x))
    {
        if(!T[fa=T[x].fa].is_root)
        {
            rotate((getson(x)==getson(fa))?fa:x);
        }
    }
}
void access(int x)
{
    int y=0;
    do
    {
        splay(x);
        T[T[x].ch[1]].is_root=true;
        T[T[x].ch[1]=y].is_root=false;
        x=T[y=x].fa;
    }while(x);
}
void mroot(int x)
{
    access(x); splay(x); pushreverse(x);
}
void link(int u,int v)
{
    mroot(u); T[u].fa=v;
}
void cut(int u,int v)
{
    mroot(u); access(v); splay(v);
    T[T[v].ch[0]].is_root=1; 
    T[u].fa=T[v].ch[0]=0;
}

做题的话看饕餮传奇’blog

尾言

每一种数据结构的学习都离不开大量的练习。 欢迎dalao斧正

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值