芝士:LCT

Posted loney-s

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了芝士:LCT相关的知识,希望对你有一定的参考价值。

背景

树链剖分只能解决静态的树上的问题,

但是对于动态的树上问题,树链剖分就凉了,LCT成为首选

虽然不知道为什么是先发明的LCT,再出现的树链剖分

主要思想

为什么树链剖分的时间复杂度小?

因为对于一堆点可以直接维护,

LCT也是一样的道理

用splay维护一堆点,splay之间相连的边称之为虚边,splay内部的边成为实边

splay的左子树上的点是他的祖先,右子树上的点是他的后代

特别说明,splay中每个点的深度是一定不一样的

操作

access

思路

将当前的点u到根节点路径上的所有的点成为一个splay上的点,

看着这个操作十分复杂,但是实际上很简单

因为我们对整棵树长成什么样子并不在意,

我们在意的只是splay之间的连接

所以我们只要将u号节点转到当前splay的根

我们定义u号节点的父亲为v

虽然v的右子树是他的后代,但是他的右子树是一定不包含v

因为他们不在一颗splay中,并且splay中的点的深度一定是互不相同的

所以我们直接将v的右子树断掉,再将u接上去就行了

就这样一直反复就行了

最开始以u号节点为根的splay的右子树也要断去

代码

void access(int u)
{
    int v=0;
    while(u)
    {
        splay(u);
        tre[u].ch[1]=v;
        push_up(u);
        v=u;
        u=tre[u].fa;
    }
}

makeroot

思路

将原树的根换到指定节点

有了access操作,这个操作也很简单

根的左子树一定是空的

所以我们先将当前节点与根节点打通

在splay一下就行了

但是因为根换了,所以需要将整颗splay打上反转的懒标记

代码

void makeroot(int u)
{
    access(u);
    splay(u);
    update_rev(u);
}

findroot

思路

找当前节点属于哪一棵原树(因为断边操作可以使其成为一个森林)

通过access操作,

一定能将当前点u和根节点打通,

但是access操作并不能保证根节点一定在splay的根节点

所以我们需要将u转到根节点,再一直往左儿子走就行了

代码

int findroot(int u)
{
    access(u);
    splay(u);
    while(tre[u].ch[0])
    {
        push_down(u);   
        u=tre[u].ch[0];
    }
    splay(u);
    return u;
}

思路

连一条u,v的边

用makeroot操作使u号节点成为根节点

再将u号节点和v号节点连一条虚边就行了

需要特判原本是否已经再一颗树上

代码

void link(int u,int v)
{
    makeroot(u);
    if(findroot(v)==u)
        return;
    tre[u].fa=v;
}

split

思路

将u和v成为在一条路径上的点

用makeroot和access操作可以很方便的实现

这里默认makeroot(u)

代码

void merge(int u,int v)
{
    makeroot(u);
    access(v);
    splay(v);
}

cut

思路

将u,v之间的边断去

先用split操作使u和v成为一条路径上的点

因为u和v之间原本是直接连边的

所以在路径上u和v是相连的,

随便将一个旋转到根之后,断去splay上的边上即可

但是这是保证操作合法情况

如果是不合法?

也就是u和v之间本来没有边

首先最容易想到的是u和v是否在一颗树上

如果合法,u和v的深度差一定是等于1的

先split(u,v)

也就是说u的右儿子如果不是v,那么就一定是不合法情况

代码

void cut(int u,int v)
{
    makeroot(u);
    if(findroot(v)!=u||tre[v].fa!=u||tre[v].ch[0])
        return;
    tre[v].fa=0;
    tre[u].ch[1]=0;
    splay(u);
}

splay

注意一下,因为我们涉及到splay的分裂与合并,所以在splay的时候就要懒标记下穿

void rotate(int x)
{
    int y=tre[x].fa;
    int z=tre[y].fa;
    int k=tre[y].ch[1]==x;
    if(!isroot(y))
        tre[z].ch[tre[z].ch[1]==y]=x;
    tre[x].fa=z;
    tre[y].ch[k]=tre[x].ch[k^1];
    tre[tre[x].ch[k^1]].fa=y;
    tre[x].ch[k^1]=y;
    tre[y].fa=x;
    push_up(y);
    push_up(x);
}
void splay(int x)
{
    down(x);
    while(!isroot(x))
    {
        int y=tre[x].fa;
        int z=tre[y].fa;
        if(!isroot(y))
        {
            if((tre[z].ch[0]==y)^(tre[y].ch[0]==x))
                rotate(x);
            else
                rotate(y);
        }
        rotate(x);
    }
    push_up(x);
}

例题

题目

这里以洛谷的P3690为例

代码

#include<iostream>
#include<cstdio>
using namespace std;
struct link_cut_tree
{
    #define MAXN 100005
    struct Splay
    {
        int ch[2];
        int fa;
        int val;
        int s;
        bool lazy_rev;
    }tre[MAXN];
    int cnt;
    #undef MAXN
    bool isroot(int u)
    {
        return tre[tre[u].fa].ch[0]!=u&&tre[tre[u].fa].ch[1]!=u;
    }
    int newnode(int val,int fa)
    {
        cnt++;
        tre[cnt].ch[0]=tre[cnt].ch[1]=0;
        tre[cnt].fa=0;
        tre[cnt].val=val;
        tre[cnt].s=val;
        tre[cnt].lazy_rev=0;
        return cnt;
    }
    void update_rev(int k)
    {
        tre[k].lazy_rev^=1;
        swap(tre[k].ch[0],tre[k].ch[1]);
    }
    void push_down(int k)
    {
        if(k==0)
            return;
        if(tre[k].lazy_rev)
        {
            tre[k].lazy_rev=0;
            update_rev(tre[k].ch[0]);
            update_rev(tre[k].ch[1]);
        }
    }
    void push_up(int k)
    {
        if(k==0)
            return;
        tre[k].s=tre[k].val^tre[tre[k].ch[0]].s^tre[tre[k].ch[1]].s;
    }
    void rotate(int x)
    {
        int y=tre[x].fa;
        int z=tre[y].fa;
        int k=tre[y].ch[1]==x;
        if(!isroot(y))
            tre[z].ch[tre[z].ch[1]==y]=x;
        tre[x].fa=z;
        tre[y].ch[k]=tre[x].ch[k^1];
        tre[tre[x].ch[k^1]].fa=y;
        tre[x].ch[k^1]=y;
        tre[y].fa=x;
        push_up(y);
        push_up(x);
    }
    void down(int u)
    {
        if(!isroot(u))
            down(tre[u].fa);
        push_down(u);
    }
    void splay(int x)
    {
        down(x);
        while(!isroot(x))
        {
            int y=tre[x].fa;
            int z=tre[y].fa;
            if(!isroot(y))
            {
                if((tre[z].ch[0]==y)^(tre[y].ch[0]==x))
                    rotate(x);
                else
                    rotate(y);
            }
            rotate(x);
        }
        push_up(x);
    }
    void access(int u)
    {
        int v=0;
        while(u)
        {
            splay(u);
            tre[u].ch[1]=v;
            push_up(u);
            v=u;
            u=tre[u].fa;
        }
    }
    void makeroot(int u)
    {
        access(u);
        splay(u);
        update_rev(u);
    }
    int findroot(int u)
    {
        access(u);
        splay(u);
        while(tre[u].ch[0])
        {
            push_down(u);   
            u=tre[u].ch[0];
        }
        splay(u);
        return u;
    }
    void merge(int u,int v)
    {
        makeroot(u);
        access(v);
        splay(v);
    }
    void link(int u,int v)
    {
        makeroot(u);
        if(findroot(v)==u)
            return;
        tre[u].fa=v;
    }
    void cut(int u,int v)
    {
        makeroot(u);
        if(findroot(v)!=u||tre[v].fa!=u||tre[v].ch[0])
            return;
        tre[v].fa=0;
        tre[u].ch[1]=0;
        splay(u);
    }
    int ask_sum(int u,int v)
    {
        merge(u,v);
        splay(u);
        return tre[u].s;
    }
    void change(int u,int x)
    {
        splay(u);
        tre[u].val=x;
        splay(u);
        return;
    }
}tre;
int n,m;
int opt;
int u,v;
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        int x;
        scanf("%d",&x);
        tre.newnode(x,0);
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%d %d %d",&opt,&u,&v);
        if(opt==0)
        {
            printf("%d
",tre.ask_sum(u,v));
        }
        if(opt==1)
        {
            tre.link(u,v);
        }
        if(opt==2)
        {
            tre.cut(u,v);
        }
        if(opt==3)
        {
            tre.change(u,v);
        }
    }
    return 0;
}

以上是关于芝士:LCT的主要内容,如果未能解决你的问题,请参考以下文章

LCT 模板及套路总结

生成函数芝士总结

luoguP2173 [ZJOI2012]网络 LCT

一些$LCT$的瓜皮题目

Js芝士点_One

Js芝士点_One