• 并不对劲的LCT


    LCT,是连猫树(link-cat-tree)的缩写。它是树链剖分和splay的结合版本。

    由于有很多关于LCT的文章以及这并不是对劲的文章,并不对劲的人并不打算讲得太详细。

    推荐:详细的LCT->

    想必大家都知道splay+树剖=LCT

    splay虽然常数较大,但是它好写好调(大部分操作都可以把左右边界转上去然后直接操作),而且还能维护序列。

    树剖是将一棵树切分成很多条链,再将它们首尾相接拼成一个序列。可以用线段树来维护序列,进行区间操作,不过并不能插入和删除。

    那么用splay来维护区间是不是就可以插入和删除了呢?想必是可以的。但是插入和删除会导致子树大小有变化,这样轻重链剖分就不适用了。不过,和splay类似地,虽然听上去很玄学,但是期望复杂度是可以被证明为log级别的;和splay类似地,并不对劲的人并不会证明。

    讲讲几个操作吧:

    1.上/下传标记,判断是左/右儿子

    void mark(int u){if(u)swap(ls,rs),re[u]^=1;}
    void pu(int u){sum[0]=0,sum[u]=sum[ls]^sum[rs]^key[u],sum[0]=0;}
    void pd(int u){if(re[u])mark(ls),mark(rs),re[u]=0;}
    int getso(int u){return son[fa[u]][0]==u?0:1;}

    和splay没什么区别。为了防止不小心pushup(0),pushup时可以在计算前后都强行令sum[0]=0。

    总结push up&down出现的地方:

    pushup:rotate,access(从下往上);

    pushdown:splay,getroot(从上往下);

    2.判断一个点是否为splay的根

    int notrt(int u){return son[fa[u]][0]==u||son[fa[u]][1]==u;}

    splay直接判断有没有父亲(我)。但是LCT是由很多棵对应一条重链的splay组成的。这样【同一树的不同splay】为了与【同一splay(父、子关系都有)】和【不同树(父、子关系都没有)】区分,是有【认父(我)不认子(你)】的规定。也就是说,splay的根可能有父亲(我),但是它的父亲(我)未必认它这个儿子(你)。

    3.旋转(rotate)

    void rot(int u)
    {
        int fu=fa[u],ffu=fa[fu],l=getso(u),fl=getso(fu),rson=son[u][l^1];
        if(notrt(fu))son[ffu][fl]=u;son[fu][l]=rson,son[u][l^1]=fu,fa[rson]=fu,fa[u]=ffu,fa[fu]=u;
        pu(rson),pu(fu),pu(u),pu(ffu);
    }

    和splay区别不大。但是被旋转点u的祖父可能和u不在同一棵splay上,要判断旋转点的父亲是不是根。

    4.伸展(splay)

    void splay(int u)
    {
        int v=u;top=0,st[++top]=v;while(notrt(v))st[++top]=v=fa[v];
        while(top)pd(st[top--]);
        while(notrt(u)){int fu=fa[u];if(notrt(fu))rot(getso(u)^getso(fu)?u:fu);rot(u);}
    }

    在普通的splay中,只有从根走到某个点才能将这个点伸展,这样肯定经过了【根到某点的路径】,那么路径上的所有标记应该已经被下传了。但是LCT中,并不会从根走到那个点。也就是说,那个点上面可能有未下传的标记。所以要先下传【根到某点的路径(是splay的根!!)】上的所有标记再进行下一步。

    5.将【根到某点的路径】变成重链(access)

    void acs(int u){for(int v=0;u;v=u,u=fa[u])splay(u),rs=v,pu(u);}

    无论怎么改重链,它们都会还在同一棵树里,所以不用改父亲(我)。

    6.求出某点所在树的根(getroot)

    int getrt(int u){acs(u),splay(u);while(u){pd(u);if(!ls)break;u=ls;}return u;}

    splay维护的序列中的数的顺序是先走重链的DFS序,所以根在DFS序中肯定比某点靠前。把根和该点放在同一个splay后,splay维护的序列的最前面的那个数就是根。

    7.将某点变成根(chroot)

    void chrt(int u){acs(u),splay(u),mark(u);}

    将该点access后,会发现它恰巧是这条重链的splay对应的序列中最靠后的。而根是最靠前的。所以把序列翻转就行了。

    8.求某两点之间路径的和(getroad)

    void getrd(int u,int v){chrt(u),acs(v),splay(v);}

    让一点为根,使两点在同一条重链上。这时这棵splay维护的序列中最靠前的是根,最靠后的是另一个点,这棵splay就是想要求和的部分。

    9.将两点之间连边(link)

    void link(int u,int v){chrt(u);if(getrt(v)!=u)fa[u]=v;}

    将一个点转到根后判断另一个点的根是不是它。由于getroot是已经将另一个点转到splay的根,直接按【认父不认子】的规则将在树根的点接到在splay根的点上。合并之后这棵树的根是v所在splay的最靠前的点。

    10.断开两点之间的边(cat)

    void cat(int u,int v){chrt(u);if(getrt(v)==u&&fa[u]==v&&!rs)fa[u]=son[v][0]=0;pu(v);}

    还是将一个点转到根。先判断v是不是在以u为根的树上(这时v已经在splay根上了),再判断它们在splay的序列中是否相邻。两条都满足就可以断了,要把父子关系都断干净。splay根的儿子情况改变,所以需要pushup。

    完整代码如下,可以在洛谷p3690提交:

    #include<iostream>
    #include<iomanip>
    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<cmath>
    #include<algorithm>
    #define maxn 300010
    #define ls son[u][0]
    #define rs son[u][1]
    using namespace std;
    inline int read()
    {
        int x=0,f=1;
        char ch=getchar();
        while(isdigit(ch)==0 && ch!='-')ch=getchar();
        if(ch=='-')f=-1,ch=getchar();
        while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
        return x*f;
    }
    inline void write(int x)
    {
        int f=0;char ch[20];
        if(!x){puts("0");return;}
        if(x<0){putchar('-');x=-x;}
        while(x)ch[++f]=x%10+'0',x/=10;
        while(f)putchar(ch[f--]);
        putchar('
    ');
    }
    struct LCT
    {
    int fa[maxn],son[maxn][3],sum[maxn],key[maxn],re[maxn],st[maxn],top;
    void mark(int u){if(u)swap(ls,rs),re[u]^=1;}
    void pu(int u){sum[0]=0,sum[u]=sum[ls]^sum[rs]^key[u],sum[0]=0;}
    void pd(int u){if(re[u])mark(ls),mark(rs),re[u]=0;}
    int getso(int u){return son[fa[u]][0]==u?0:1;}
    int notrt(int u){return son[fa[u]][0]==u||son[fa[u]][1]==u;}
    void rot(int u)
    {
        int fu=fa[u],ffu=fa[fu],l=getso(u),fl=getso(fu),rson=son[u][l^1];
        if(notrt(fu))son[ffu][fl]=u;son[fu][l]=rson,son[u][l^1]=fu,fa[rson]=fu,fa[u]=ffu,fa[fu]=u;
        pu(rson),pu(fu),pu(u),pu(ffu);
    }
    void splay(int u)
    {
        int v=u;top=0,st[++top]=v;while(notrt(v))st[++top]=v=fa[v];
        while(top)pd(st[top--]);
        while(notrt(u)){int fu=fa[u];if(notrt(fu))rot(getso(u)^getso(fu)?u:fu);rot(u);}
        pu(u);
    }
    void acs(int u){for(int v=0;u;v=u,u=fa[u])splay(u),rs=v,pu(u);}
    int getrt(int u){acs(u),splay(u);while(u){pd(u);if(!ls)break;u=ls;}return u;}
    void chrt(int u){acs(u),splay(u),mark(u);}
    void getrd(int u,int v){chrt(u),acs(v),splay(v);}
    void link(int u,int v){chrt(u);if(getrt(v)!=u)fa[u]=v;}
    void cat(int u,int v){chrt(u);if(getrt(v)==u&&fa[u]==v&&!rs)fa[u]=son[v][0]=0;pu(v);}
    }t;
    int main()
    {
        int n=read(),q=read(),f,x,y;
        for(int i=1;i<=n;i++)t.key[i]=read();
        while(q--)
        {
            f=read(),x=read(),y=read();
            if(f==0)t.getrd(x,y),write(t.sum[y]);
            if(f==1)t.link(x,y);
            if(f==2)t.cat(x,y);
            if(f==3)t.splay(x),t.key[x]=y;
        }
        return 0;
    }
    View Code

    当然,如果乐意卡常的话…

    #include<iostream>
    #include<iomanip>
    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<cmath>
    #include<algorithm>
    #define ls son[u][0]
    #define rs son[u][1]
    #define rep(i,x,y) for(register int i=(x);i<=(y);i++)
    #define dwn(i,x,y) for(register int i=(x);i>=(y);i--)
    #define Int register int
    #define maxn 300010
    using namespace std;
    inline int read()
    {
        Int x=0,f=1;
        char ch=getchar();
        while(isdigit(ch)==0 && ch!='-')ch=getchar();
        if(ch=='-')f=-1,ch=getchar();
        while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
        return x*f;
    }
    inline void write(Int x)
    {
        Int f=0;char ch[20];
        if(!x){puts("0");return;}
        if(x<0){putchar('-');x=-x;}
        while(x)ch[++f]=x%10+'0',x/=10;
        while(f)putchar(ch[f--]);
        putchar('
    ');
    }
    int fa[maxn],son[maxn][3],sum[maxn],key[maxn],re[maxn],st[maxn],top;
    void mark(Int u){if(u)swap(ls,rs),re[u]^=1;}
    void pu(Int u){sum[u]=sum[ls]^sum[rs]^key[u];}
    void pd(Int u){if(re[u])mark(ls),mark(rs),re[u]=0;}
    int getso(Int u){return son[fa[u]][0]!=u;}
    int nort(Int u){return son[fa[u]][0]==u || son[fa[u]][1]==u;}
    void rot(Int u)
    {
        Int fu=fa[u],ffu=fa[fu],l=getso(u),r=l^1,fl=getso(fu),rson=son[u][r];
        if(nort(fu))son[ffu][fl]=u;son[fu][l]=rson,son[u][r]=fu,fa[rson]=fu,fa[u]=ffu,fa[fu]=u;
        pu(fu),pu(u);
    }
    void splay(Int u)
    {
        Int v=u;top=0;st[++top]=v;while(nort(v))st[++top]=v=fa[v];
        while(top)pd(st[top--]);
        while(nort(u)){if(nort(fa[u]))rot(getso(u)^getso(fa[u])?u:fa[u]);rot(u);}
    }
    void acs(Int u){for(Int v=0;u;v=u,u=fa[u])splay(u),rs=v,pu(u);}
    void chrt(Int u){acs(u),splay(u),mark(u);}
    int getrt(Int u){acs(u),splay(u);while(u){pd(u);if(!ls)return u;u=ls;}}
    void getrd(Int u,Int v){chrt(u),acs(v),splay(v);}
    void link(Int u,Int v){chrt(u);if(getrt(v)!=u)fa[u]=v;}
    void cut(Int u,Int v){chrt(u);if(getrt(v)==u&&fa[u]==v&&!rs)fa[u]=son[v][0]=0;}
    int main()
    {
        Int n=read(),q=read(),f,x,y;
        rep(i,1,n)key[i]=read();
        while(q--)
        {
            f=read(),x=read(),y=read();
            if(!f)getrd(x,y),write(sum[y]);
            if(f==1)link(x,y);
            if(f==2)cut(x,y);
            if(f==3)splay(x),key[x]=y;
        }
        return 0;
    }
    View Code

    不知道这个模板能不能在5分钟之内打完?据说最快7分钟?

  • 相关阅读:
    Windows下使用Visual Studio Code搭建Go语言环境
    无缓冲和带缓冲channel的区别
    Asp.Net MVC如何返回401响应码
    从这里开始我的博客园
    java判定字符串中仅有数字和- 正则表达式匹配 *** 最爱那水货
    主席树
    Mybitis+springMVC 套路
    jeeplus ani 文档路径
    jquery easyui datagrid 多选只能获取一条数据
    python写入文件编码报错
  • 原文地址:https://www.cnblogs.com/xzyf/p/8549832.html
Copyright © 2020-2023  润新知