码迷,mamicode.com
首页 > 其他好文 > 详细

芝士:LCT

时间:2020-01-10 22:21:04      阅读:85      评论:0      收藏:0      [点我收藏+]

标签:动态   十分   反转   静态   down   www   开始   split   不包含   

背景

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

但是对于动态的树上问题,树链剖分就凉了,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\n",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

标签:动态   十分   反转   静态   down   www   开始   split   不包含   

原文地址:https://www.cnblogs.com/loney-s/p/12178218.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!