【树】动态树 LCT

动态树(LCT)

学习笔记: Yang Zhe QTREE解法的一些研究 , OIWiki

时间复杂度: (mathcal{O(nlogn)})

前置知识: splay, 树链剖分

理解

  • 可以简单把 LCT 理解成用一些 Splay 来维护动态的树链剖分,一期实现动态树上的区间操作。

  • 对于每条实链,建一个 Splay 来维护整个链的区间。

  • 每棵 Splay 维护原树中的一条路径,且中序遍历这颗 Splay 得到的点序列,从前到后对应原树 从上到下的一条路径。

  • 在 LCT 中,每棵 Splay 的根节点的父亲节点指向原树中 这条链 的父亲节点。

变量声明

  • ch[N][2] 左右儿子
  • f[N] ⽗亲指向
  • sum[N] 路径权值和
  • val[N] 点权
  • tag[N] 翻转标记

函数声明

Splay 系函数

  1. Get(x) 获取 是父亲的哪个儿子。
  2. Splay(x) 通过和 Rotate 操作联动实现把 旋转到当前 Splay 的根。
  3. Rotate(x) 将 向上旋转一层的操作。

新操作

  1. Access(x) 把从根到 的所有点放在⼀条实链里,使根到 成为一条实路径,并且在同一棵 Splay 里。 只有此操作是必须实现的,其他操作视题目而实现。
  2. IsRoot(x) 判断 是否是所在树的根。
  3. Update(x)Access 操作之后,递归地从上到下 Pushdown 更新信息。
  4. MakeRoot(x) 使 点成为其所在树的根。
  5. Link(x, y) 在 两点间连一条边。
  6. Cut(x, y) 把 两点间边删掉。
  7. Find(x) 找到 所在树的根节点编号。
  8. Fix(x, v) 修改 的点权为 。
  9. Split(x, y) 提取出 间的路径,方便做区间操作。

模板

//LCT
struct LCT{
    int ch[maxn][2],f[maxn],tag[maxn],val[maxn],sum[maxn];
    inline int getch(int x){return ch[f[x]][1]==x;}
    inline int isroot(int x){return ch[f[x]][0]!=x&&ch[f[x]][1]!=x;}
    inline void pushdown(int p){
        if(tag[p]){
            if(ch[p][0])swap(ch[ch[p][0]][0],ch[ch[p][0]][1]),tag[ch[p][0]]^=1;
            if(ch[p][1])swap(ch[ch[p][1]][0],ch[ch[p][1]][1]),tag[ch[p][1]]^=1;
            tag[p]=0;
        }
    }
    inline void pushup(int x){
        if(ch[x][0])pushdown(ch[x][0]);
        if(ch[x][1])pushdown(ch[x][1]);
        sum[x]=(val[x]+sum[ch[x][0]]+sum[ch[x][1]])%mod;
    }
    void update(int p){
        if (!isroot(p))update(f[p]);
        pushdown(p);
    }
    inline void rotate(int x){
        int y=f[x],z=f[y],k=getch(x);
        if (!isroot(y))ch[z][ch[z][1]==y]=x;
        ch[y][k]=ch[x][!k],f[ch[x][!k]]=y;
        ch[x][!k]=y,f[y]=x,f[x]=z;
        pushup(x),pushup(y);
    }
    //Splay(x) 通过和 Rotate 操作联动实现把 x 旋转到当前 Splay 的根
    inline void splay(int x){
        update(x);
        for(int fa;fa=f[x],!isroot(x);rotate(x)){
            if(!isroot(fa))rotate(getch(fa)==getch(x)?fa:x);
        }
        pushup(x);
    }
    //Access(x) 把从根到 x 的所有点放在一条实链里,使根到 x 成为一条实路径,并且在同一棵Splay里
    //返回值y 则此时x到当前根的路径恰好构成一个Splay,且该Splay的根为y
    inline int access(int x){
        int p;
        for(p=0;x;p=x,x=f[x]){
            splay(x);ch[x][1]=p;pushup(x);
        }
        return p;
    }
    //MakeRoot(x) 使x点成为其所在树的根
    inline void makeroot(int p){
        access(p);splay(p);
        swap(ch[p][0],ch[p][1]);
        tag[p]^=1;
    }
    //在x,y两点间连一条边
    inline void link(int x,int p){
        makeroot(x);
        splay(x);
        f[x]=p;
    }
    // split 拿出一颗Splay 维护x到y的路径
    inline void split(int x,int y){
        makeroot(x);access(y);splay(y);
    }
    //把x,y两点间的边删掉
    inline void cut(int x,int p){
        split(x,p);
        if(ch[p][0]==x&&!ch[x][1])ch[p][0]=f[x]=val[x]=0;
    }
    //如果有 Find(x)==Find(y),则说明x,y两点在一棵树上,相互连通
    inline int find(int p){
        access(p);splay(p);
        while(ch[p][0])p=ch[p][0];
        splay(p);
        return p;
    }
    inline int lca(int x,int y){access(x);return access(y);}
    void print(int x) {
        if (!x) return;
        pushdown(x);
        printf("(");
        print(ch[x][0]);
        printf(")%d[", x);
        print(ch[x][1]);
        printf("]");
    }
}st;
原文地址:https://www.cnblogs.com/kkkek/p/13637151.html