• LCT


    LCT

    给定n个点以及每个点的权值,要你处理接下来的m个操作。操作有4种。操作从0到3编号。点从1到n编号。0:后接两个整数(x,y),代表询问从x到y的路径上的点的权值的xor和。保证x到y是联通的。1:后接两个整数(x,y),代表连接x到y,若x到y已经联通则无需连接。2:后接两个整数(x,y),代表删除边(x,y),不保证边(x,y)存在。3:后接两个整数(x,y),代表将点x上的权值变成y。

    首先来考虑一下没有操作1和操作2,怎么做。没错!这就是个树剖。但是1和2要求我们动态维护树链,所以说很难受,没法用树剖做。

    怎么办呢?我们可以用LCT!LCT是动态树的一种,支持在动态维护树的连接和断开的同时维护树链。具体来说,LCT中的每个树链是由splay来维护的,splay中的点的key是点的深度。由于确定了树根以后,树链上的点两两之间深度不同,所以不会出现两个点的key相同的情况。

    原树中,链与链之间是有边的,但是一条链最多连出一条边,就是链顶结点连向上方的边。我们把链中的边称作实边,链与链之间的边称作虚边,那么虚边和实边显然共同组成了原树的所有边。

    lct中,一个splay的key值相邻的两个点之间可以看作连有一条实边,同时splay的根又向上连了一条边,代表这条链连出的那条虚边。因此,lct中保存了原树的信息。我们在操作lct的时候要保证这些信息不丢失。

    access是lct最基本的操作。它表示把x到root之间的路径打通变成一条链。

    void access(int x){  //y:上一个splay的根
            for (int y=0; x; y=x, x=fa[x]){
                splay(x); son[x][1]=y; pushup(x); }
        }
    

    y代表前面处理过的那条链的splay根,x就是y这条链顶端所连的点。splay(x)之后,在x右边的点深度都大于x。把它们舍弃掉以后接上y,表示比x深度大的点是y这些点了。

    由于给x接上了点,别忘记更新x的值。

    接下来是makeroot操作,它把x变成原树的根。

    void makert(int x){ access(x); splay(x); rev[x]^=1; }
    

    思路很简单。先把x到根的路径打通。x要变成原树的根,就相当于这条链被翻转,所以把x变成splay的根后,在x上打一个rev标记,这样所有点的深度顺序都被反过来了,x也从深度最大的点变成了深度最小的点。

    下面是find操作,它寻找原树的根。没什么好讲的,就是access以后在splay中不停往左走,找到深度最小的点。

    接下来是get操作,get(x, y)表示拎出一条以x和y为端点的链,把x变成树根,并把y变成这条链splay的根。

    void get(int x, int y){ makert(x); access(y); splay(y); }
    

    get没啥好说的。接下来终于到了我们需要的两个操作——link和cut。

    link操作把x和y连在一起,相当于合并了两棵树。

    void link(int x, int y){ makert(x); fa[x]=y; }
    

    最后就是cut操作了,它把x和y的边断开。同时还顺便判断了一下x和y是不是有边:

    void cut(int x, int y){  //仅当有边时才能断
            get(x, y);
            if (fa[x]==y&&!son[x][1]) fa[x]=son[y][0]=0;
            pushup(y);
        }
    

    显然,只有当x和y是key值相邻的点时才会有边。因此如果key值不相邻,那就可以直接不管它。别忘记y的值需要更新。

    贴一下完整代码~

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    
    const int maxn=3e5+5;
    struct LCT{
        int son[maxn][2], fa[maxn], rev[maxn], sum[maxn], w[maxn];
        inline int which(int x){ return son[fa[x]][0]==x?0:1; }
        inline bool isrt(int x){ return son[fa[x]][0]!=x&&son[fa[x]][1]!=x; }
        inline void link(int f, int x, int p){ son[f][p]=x; fa[x]=f; }
        inline void pushup(int x){
            sum[x]=sum[son[x][0]]^sum[son[x][1]]^w[x]; }
        inline void pushdown(int x){
            rev[son[x][0]]^=rev[x]; rev[son[x][1]]^=rev[x];
            if (rev[x]){ swap(son[x][0], son[x][1]); rev[x]=0; }
        }
        void spin(int x){
            int f=fa[x], ff=fa[f]; bool frt=isrt(f);
            int wx=which(x), wf=which(f);
            link(f, son[x][!wx], wx); link(x, f, !wx);
            if (!frt) link(ff, x, wf); else fa[x]=ff;
            //必须这样写,因为它们有且只有它们两个的信息变动了
            //然后就不用管sum的维护了
            pushup(f); pushup(x);
        }
        void flagclear(int x){
            if (!isrt(x)) flagclear(fa[x]);
            pushdown(x); }
        void splay(int x){  //splay后一条链上的标记都被清掉了
            flagclear(x);
            for (; !isrt(x); spin(x))
                if (!isrt(fa[x])) spin(which(x)==which(fa[x])?fa[x]:x);
        }
        void access(int x){  //y:上一个splay的根
            for (int y=0; x; y=x, x=fa[x]){
                splay(x); son[x][1]=y; pushup(x); }
        }
        void makert(int x){ access(x); splay(x); rev[x]^=1; }
        int find(int x){
            access(x); splay(x); pushdown(x);
            while (son[x][0]) x=son[x][0], pushdown(x);
            return x;
        }
        //把x提根以后,打通y,用y当splay的根
        void get(int x, int y){ makert(x); access(y); splay(y); }
        void link(int x, int y){ makert(x); fa[x]=y; }
        void cut(int x, int y){  //仅当有边时才能断
            get(x, y);
            if (fa[x]==y&&!son[x][1]) fa[x]=son[y][0]=0;
            pushup(y);
        }
    }lct;
    
    int n, m;
    
    int main(){
        scanf("%d%d", &n, &m); int op, x, y;
        for (int i=1; i<=n; ++i) scanf("%d", &lct.w[i]);
        for (int i=1; i<=m; ++i){
            scanf("%d%d%d", &op, &x, &y);
            if (op==0) lct.get(x, y), printf("%d
    ", lct.sum[y]);
            if (op==1) if (lct.find(x)!=lct.find(y)) lct.link(x, y);
            if (op==2) if (lct.find(x)==lct.find(y)) lct.cut(x, y);
            if (op==3) lct.w[x]=y, lct.splay(x);
        }
        return 0;
    }
    

    显然,lct中的所有操作去除access都是(O(logn))的。那么access操作的复杂度是多少呢?没错,它就是(O(logn))!别问我为什么,钦定的。(逃)

  • 相关阅读:
    perl get请求发送json数据
    一招教你玩转SQL:通过找出数据的共同属性实现SQL需求
    Uncaught (in promise) Error: Component series.map not exists. Load it first
    python post 参数
    python 发送json数据
    毫秒级从百亿大表任意维度筛选数据,是怎么做到的…
    python 日期处理
    python urlencode urldecode
    在服务器本地监控服务端口命令之ss
    在服务器本地监控服务端口命令之ss
  • 原文地址:https://www.cnblogs.com/MyNameIsPc/p/9502375.html
Copyright © 2020-2023  润新知