[模板] 动态树/LCT

本文深入探讨了LCT(Link-Cut Tree)数据结构,一种高效处理动态树操作的工具。通过使用splay树维护重链,LCT能够实现在O(logn)的时间复杂度下进行动态加边、删边及维护链上信息。文章详细介绍了LCT的基本概念、核心操作如Access、MakeRoot、FindRoot、Link、Cut和Split,以及其实现细节和应用场景,包括动态树、动态最小生成树和并查集优化等。

简介

LCT是一种数据结构, 可以维护树的动态加边, 删边, 维护链上信息(满足结合律), 单次操作时间复杂度 \(O(\log n)\).(不会证)

思想类似树链剖分, 因为splay可以换根, 用splay维护重链, splay的中序遍历即为链按深度从小到大遍历的结果.

操作

注意区分splay和整棵树的区别, splay根和树根的区别.

\(Access(p)\) 操作指的是将p与根放在同一棵splay中.

\(MakeRoot(p)\) 操作指的是将p变为它所在树(而不是splay)的根. 由于维护的是链上信息, 这不会对答案产生影响.

\(FindRoot(p)\) 操作指的是求p所在树(而不是splay)的根.

\(Link(x,y)\) 操作指的是如果p与q不在同一棵树中, 那么连边 \((x,y)\).

\(Cut(x,y)\) 操作指的是如果p与q有边相连, 那么连边 \((x,y)\).
其中, 'p与q有边相连' 等价于同时满足:
- p,q在同一棵树中.
- p,q深度相差1.(splay中p,q中序遍历时相邻)

\(Split(x,y)\) 操作指的是取出 \((x,y)\) 这个链, 并放在一棵splay中.

具体实现见代码.

应用

  • 动态树(代替树链剖分)
  • 动态最小生成树
  • 维护子树信息(AAA树)
  • 维护并查集(e.g. 可持久化并查集)
  • 优化dinic(据tarjan论文)(怕不是会写死)

Code

const int nsz=3e5+50;
int n,val[nsz];
struct tlct{
    struct tnd{int ch[2],fa,sum,rv;}tree[nsz];
#define ls(p) tree[p].ch[0]
#define rs(p) tree[p].ch[1]
#define fa(p) tree[p].fa
#define dir(p) (rs(fa(p))==p)
    bool isrt(int p){return ls(fa(p))!=p&&rs(fa(p))!=p;}//splay rt
    void rev(int p){swap(ls(p),rs(p));tree[p].rv^=1;}
    void pu(int p){
        tree[p].sum=tree[ls(p)].sum^val[p]^tree[rs(p)].sum;
    }
    void pd(int p){
        if(tree[p].rv==0)return;
        if(ls(p))rev(ls(p));
        if(rs(p))rev(rs(p));
        tree[p].rv=0;
    }
    void pdt(int p){//push down whole splay; from top to bottom
        if(!isrt(p))pdt(fa(p));
        pd(p);
    }
    void rotate(int p){//fa(p) should exist
        int x=fa(p),y=fa(x),dir1=dir(p),dir2=dir(x),z=tree[p].ch[dir1^1];
        if(!isrt(x))tree[y].ch[dir2]=p;fa(p)=y;
        tree[p].ch[dir1^1]=x;fa(x)=p;
        tree[x].ch[dir1]=z;if(z)fa(z)=x;
        pu(x),pu(p);//can't swap
    }

    void splay(int p){
        pdt(p);
        while(!isrt(p)){
            if(!isrt(fa(p)))rotate(dir(p)==dir(fa(p))?fa(p):p);
            rotate(p);
        }
    }

    void access(int p){
        for(int y=0;p;y=p,p=fa(p)){
            splay(p),rs(p)=y;
            pu(p);
        }
    }
    void makert(int p){//p -> tree rt & splay rt
        access(p),splay(p);
        rev(p);
    }
    int findrt(int p){//find tree rt; p -> splay rt
        access(p),splay(p);
        while(ls(p))pd(p),p=ls(p);
        splay(p); //不加会tle... 不知道为什么
        return p;
    }
    bool iscon(int x,int y){return findrt(x)==findrt(y);}
    void split(int x,int y){//x -> tree rt; y -> splay rt
        makert(x);
        access(y);
        splay(y);
    }
    void link(int x,int y){
        makert(x);
        if(findrt(y)!=x)fa(x)=y;
    }
    void cut(int x,int y){
        split(x,y);
        if(fa(x)==y&&rs(x)==0)
            fa(x)=ls(y)=0,pu(y);
    }
    void pr(){
        rep(i,1,n)printf("nd=%d fa=%d ls=%d rs=%d sum=%d rv=%d\n",i,fa(i),ls(i),rs(i),tree[i].sum,tree[i].rv);
    }
}lct;

//init
rep(i,1,n)tree[i].sum=val[i];

//query a chain
    lct.split(b,c);
    ans=lct.tree[c].sum;

//modify one point
    lct.makert(b);
    val[b]=c;
    lct.pu(b);

转载于:https://www.cnblogs.com/ubospica/p/10270646.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值