平衡树+LCT全纪录

平衡树(splay)

平衡数模板

平衡树能干些什么呢?

  • 插入一个数

  • 删除一个数

  • 查询数 x x 的排名(小于x的元素个数)

  • 查询排名为 x x 的数

  • 查询x的前驱(小于 x x 且最大的数)

  • 查询x的后继(大于 x x 且最小的数)

  • 区间翻转

  • 区间加乘(像线段树一样打标记即可)

但是平衡树有一个缺陷,不能在区间层面上进行上述操作

所以我们可以在外面套上一层线段树(线段树套splay
线段树的每一个结点都是一棵 splay s p l a y
这样我们在构建的时候,只能单点插入,而且一条路上的每一个结点都要insert,同理,修改也只支持单点

线段树套 splay s p l a y ,基本上只具有线段树询问区间的功能
并且支持 splay s p l a y 的几乎所有功能

  • 插入一个数

  • 删除一个数

  • 查询数 x x 的排名(小于x的元素个数)

  • 查询排名为 x x 的数(二分判定)

  • 查询x的前驱(小于 x x 且最大的数)
    (分成logn个区间,每个区间求前驱,最后求 max m a x

  • 查询 x x 的后继(大于x且最小的数)
    (分成 logn l o g n 个区间,每个区间求后继,最后求 min m i n

  • 区间翻转(没写过)

  • 区间加乘(像线段树一样打标记即可)

所以说如果发现一道题需要支持上述操作,我们就可以考虑 splay s p l a y
(不过如果只有查询元素排名以及排名为k的元素,还是先考虑一下主席树,毕竟还是代码复杂度越低越好)

经典例题:splay区间翻转
经典例题:线段树+splay(2B)
经典例题:线段树+splay(逆序对个数)
经典例题:splay(括号)
经典例题:splay(项链工厂)

splay s p l a y 最基础的操作
注意 splay s p l a y 函数和 del d e l 函数的写法
insert i n s e r t 的时候不要忘了 update(last) u p d a t e ( l a s t )

如果要加上 rever r e v e r
我们先调用 find_pm f i n d _ p m 夹逼我们需要翻转的区间
之后再这个区间打上翻转标记即可
不过,这样我们就需要在 splay s p l a y 之前 down d o w n 一下
每次 find f i n d 的时候也需要 push p u s h (多 push p u s h 没有什么坏处)

#include<bits/stdc++.h>

using namespace std;

const int INF=1e9;
const int N=100010;
int root=0,top=0,pre[N],ch[N][2],size[N],v[N],cnt[N];
bool rev[N]; 

void clear(int bh) {
    pre[bh]=ch[bh][0]=ch[bh][1]=size[bh]=v[bh]=cnt[bh]=0;
    //rev[bh]=0;
}

int get(int bh) {
    return ch[pre[bh]][0]==bh? 0:1;
}

void push(int bh) {
    if (!bh||!rev[bh]) return;
    if (ch[bh][0]) rev[ch[bh][0]]^=1;
    if (ch[bh][1]) rev[ch[bh][1]]^=1;
    swap(ch[bh][0],ch[bh][1]);
}

void down(int bh) {
    if (pre[bh]) down(pre[bh]);
    push(bh);
}

void update(int bh) {
    if (!bh) return;
    size[bh]=cnt[bh];
    if (ch[bh][0]) size[bh]+=size[ch[bh][0]];
    if (ch[bh][1]) size[bh]+=size[ch[bh][1]];
}

void rotate(int bh) {
    int fa=pre[bh];
    int grand=pre[fa];
    int wh=get(bh);
    ch[fa][wh]=ch[bh][wh^1];
    pre[ch[fa][wh]]=fa;
    ch[bh][wh^1]=fa;
    pre[fa]=bh;
    pre[bh]=grand;
    if (grand) ch[grand][ch[grand][0]==fa? 0:1]=bh;
    update(fa);
    update(bh);
}

void splay(int bh,int mb) {
    //down(bh);
    for (int fa;(fa=pre[bh])!=mb;rotate(bh))      
        if (pre[fa]!=mb)                            //pre[fa]!=mb
            rotate(get(bh)==get(fa)? fa:bh);
    if (!mb) root=bh;
}

void insert(int x) {
    int now=root;
    int last=0;
    if (!root) {
        top++;
        pre[top]=0; ch[top][0]=ch[top][1]=0; 
        size[top]=1; cnt[top]=1; v[top]=x;
        root=top;                                    //root=top;
        return;
    }
    while (1) {
        //push(now);
        if (v[now]==x) {
            cnt[now]++;
            update(now);
            update(last);                            //update(last)
            splay(now,0);
            return;
        }
        last=now;
        now=ch[now][v[now]<x];
        if (!now) {
            top++;
            pre[top]=last; ch[top][0]=ch[top][1]=0; 
            size[top]=1; cnt[top]=1; v[top]=x;
            ch[last][v[last]<x]=top;
            update(last);
            splay(top,0);
            return;
        }
    }
}

int find_pm(int x) {
    int now=root,ans=0;
    while (now) {
        //push(now);
        if (v[now]>x) now=ch[now][0];
        else {
            ans+=(ch[now][0]? size[ch[now][0]]:0);    //先维护ans
            if (v[now]==x) {
                splay(now,0);
                return ans+1;
            }
            ans+=cnt[now];
            now=ch[now][1];
        }
    }
    return 0;
}

int find_x(int k) {
    int now=root;
    while (now) {
        //push(now);
        if (ch[now][0]&&size[ch[now][0]]>=k) now=ch[now][0];
        else {
            int tmp=(ch[now][0])? size[ch[now][0]]:0;
            tmp+=cnt[now];
            if (tmp>=k) return now;
            k-=tmp;
            now=ch[now][1];
        }
    }
    return 0;
} 

int qian() {
    int x=ch[root][0];
    while (ch[x][1]) x=ch[x][1];
    return x;
}

void del(int x) {
    find_pm(x);
    if (cnt[root]>1) {
        cnt[root]--;
        update(root);
        return;
    }
    if (!ch[root][0]&&!ch[root][1]) {
        clear(root);
        root=0;
        return;
    }
    if (!ch[root][0]) {
        int k=root;
        root=ch[k][1];
        pre[root]=0;
        clear(k);
        return;
    }
    if (!ch[root][1]) {
        int k=root;
        root=ch[k][0];
        pre[root]=0;
        clear(k);
        return;
    }
    int k=root;
    int p=qian();
    splay(p,0);
    ch[root][1]=ch[k][1];
    pre[ch[root][1]]=root;
    update(root);
    clear(k);
}

int fro(int x) {
    int ans=0;
    int now=root;
    while (now) {
        //push(now);
        if (v[now]>=x) now=ch[now][0];
        else {
            ans=max(ans,v[now]);
            now=ch[now][1];
        }
    }
    return ans;
}

int nxt(int x) {
    int ans=INF;
    int now=root;
    while (now) {
        //push(now);
        if (v[now]<=x) now=ch[now][1];
        else {
            ans=min(ans,v[now]);
            now=ch[now][0];
        }
    }
    return ans;
}

int main()
{
    int x,opt;
    int n;
    scanf("%d",&n);
    while (n--) {
        scanf("%d%d",&opt,&x);
        if (opt==1) insert(x);
        else if (opt==2) del(x);
        else if (opt==3) 
            printf("%d\n",find_pm(x));
        else if (opt==4) 
            printf("%d\n",v[find_x(x)]);
        else if (opt==5) 
            printf("%d\n",fro(x));      //不保证数据中存在x 
        else 
            printf("%d\n",nxt(x));      //不保证数据中存在x 
    } 
    return 0;
}

insert i n s e r t 函数支持单点插入
如果我们有初始序列,那么推荐如此建树:

int build(int l,int r,int fa)
{
    if (l>r) return 0;
    int mid=(l+r)>>1;
    int now=++top;
    ch[now][0]=build(l,mid-1,now);
    ch[now][1]=build(mid+1,r,now);
    pre[now]=fa;
    rev[now]=0;
    v[now]=a[mid]; 
    update(now);
    return now;
}

LCT

LCT简单讲解
LCT维护子树信息(难)

实际上 LCT L C T 就是动态的多个 splay s p l a y ,重点还是理解操作
(注意,LCT一定是一棵TREE)

LCT L C T 中最重要的两个操作(除了 splay s p l a y rotate r o t a t e )就是 expose e x p o s e makeroot m a k e r o o t

expose e x p o s e

访问一个结点,该结点到根的路径就会变成偏爱路径,在一棵 splay s p l a y

void expose(int x) {
    int t=0;
    while (x) {
        splay(x);
        ch[x][1]=t;
        update(x);           //产生了新儿子,需要update 
        t=x;
        x=pre[x];
    }
}
makeroot m a k e r o o t

换根,把树形结构的根换为 x x

void makeroot(int x) {
    expose(x);
    splay(x);
    rev[x]^=1;
}

其余的所有操作都是建立在这两个操作上的

link(x,y)

我们要将 x x y连接起来
简单的,我们把 x x 连到y
明确 x,y x , y 一定在不同的 splay s p l a y 内(废话,ta们都不连通)
那么我们首先需要 x x 没有father,这样我们才能放心大胆的把ta连向 y y
什么样的结点没有father?根结点!
所以我们 makeroot(x) m a k e r o o t ( x )
因为我们没有访问过 x<>y x < − > y 这条路径,所以 x x 不会是y的偏爱儿子
换句话说, y y 是不会认x这个儿子的
所以我们只要 pre[x]=y p r e [ x ] = y 就可以了

void link(int x,int y) {
    makeroot(x);
    pre[x]=y;
}

cut(x,y) c u t ( x , y )

和link一样,我们需要把 x x 变成树的根:makeroot(x)
之后expose(y),splay(y)
我们就得到了一棵以 y y 为根,x<>y路径的 splay s p l a y
x x 是根节点,ta的深度最小,y是辅助树的根节点
所以 x x 一定是y的左儿子
pre[x]=0,ch[y][0]=0 p r e [ x ] = 0 , c h [ y ] [ 0 ] = 0

void cut(int x,int y) {
    makeroot(x);
    expose(y);
    splay(y);
    ch[y][0]=pre[x]=0;
    //update(y)
}
find f i n d

这个操作可以找到 x x 结点在原树中的根结点
(根结点的深度最小,所以在splay中最靠左)

int find(int x) {
    expose(x);
    splay(x);
    while (ch[x][0]) x=ch[x][0];
    return x;
}
两点连通性
bool linked(int x,int y) {
    return find(x)==find(y);
}
路径权值和

LCT的优点就是ta的形态不固定
我们可以把路径中的一个端点视为根节点(比如说x): makeroot(x) m a k e r o o t ( x )
expose(y) e x p o s e ( y ) x x y的路径就变成了偏爱路径,
splay(y) s p l a y ( y ) y y 节点上的sum即为路径权值和

int ask_sum(int x,int y) {
    makeroot(x);
    expose(y); splay(y);
    return sum[y];
}
节点到根的距离:

同理,返回 size s i z e 即可

int ask_dis(int x,int y) {
    makeroot(x);
    expose(y); splay(y);
    return size[y];
}
更改节点值

我们要改变一个结点的权值, 我们当然希望涉及到的结点尽量少
什么样的结点改变ta的值影响的结点维护值最少?根节点!
所以我们首先 makeroot(x) m a k e r o o t ( x )
直接修改 x x 的值就可以了
因为我们改变了这个结点的状态,所以需要update(x)

void change_point(int x,int z) {
    makeroot(x);
    v[x]=z;
    update(x);
}
更改路径值

这个操作比较厉害,可以处理路径加乘的问题
我们像线段树的加乘那样每个结点维护加标记和乘标记
每次在push的时候,维护值以及标记
mul[son]=mul[son]mul[fa],ad[son]=ad[son]mul[fa]+ad[fa] m u l [ s o n ] = m u l [ s o n ] ∗ m u l [ f a ] , a d [ s o n ] = a d [ s o n ] ∗ m u l [ f a ] + a d [ f a ]

void cal(int x,int a,int b) {     //*a   +b
    if (!x) return;
    v[x]=v[x]*a+b;                //不要忘了维护单点值 
    sum[x]=sum[x]*a+size[x]*b;
    ad[x]=ad[x]*a+b;
    mul[x]=mul[x]*a;
}

void push(int bh) {
    if (!bh) return;
    if (rev[bh]) {
        ...
    }
    if (ad[bh]!=0||mul[bh]!=1) {
        if (ch[bh][0]) cal(ch[bh][0],mul[bh],ad[bh]);
        if (ch[bh][1]) cal(ch[bh][1],mul[bh],ad[bh]);
    }
    mul[bh]=1; ad[bh]=0;
}

void add(int x,int y,int z) {
    makeroot(x);
    expose(y); splay(y);
    cal(y,1,z);
}

void multi(int x,int y,int z) {
    makeroot(x);
    expose(y); splay(y);
    cal(y,z,0);
}

终上所述,需要询问树上路径权值,同时需要支持加边删边操作的题目,可以考虑LCT

经典例题:LCT基本操作
经典例题:LCT维护路径加乘
经典例题:LCT+并查集(一)
经典例题:LCT+并查集(二)
经典例题:LCT+SAM

LCT中也要判断:if (!bh) return;

const int N=100010;
int pre[N],ch[N][0],size[N],sum[N],v[N],ad[N],mul[N];
bool rev[N];
int q[N];

int isroot(int bh) {
    return ch[pre[bh]][0]!=bh&&ch[pre[bh]][1]!=bh;
}

int get(int bh) {
    return ch[pre[bh]][0]==bh? 0:1;
}

void update(int bh) {
    if (!bh) return;
    size[bh]=1;
    if (ch[bh][0]) size[bh]+=size[ch[bh][0]];
    if (ch[bh][1]) size[bh]+=size[ch[bh][1]];
    sum[bh]=v[bh];
    if (ch[bh][0]) sum[bh]+=sum[ch[bh][0]];
    if (ch[bh][1]) sum[bh]+=sum[ch[bh][1]];
}

void rotate(int bh) {
    int fa=pre[bh];
    int grand=pre[fa];
    int wh=get(bh);
    if (!isroot(fa)) ch[grand][ch[grand][0]==fa? 0:1]=bh;
    pre[bh]=grand;
    ch[fa][wh]=ch[bh][wh^1];
    pre[ch[fa][wh]]=fa;
    ch[bh][wh^1]=fa;
    pre[fa]=bh;
    update(fa);
    update(bh);
}

void push(int bh) {
    if (!rev[bh]||!bh) return;
    if (ch[bh][0]) rev[ch[bh][0]]^=1;
    if (ch[bh][1]) rev[ch[bh][1]]^=1;
    swap(ch[bh][0],ch[bh][1]);
    rev[bh]=0;
}

void splay(int bh) {
    int top=0;
    q[++top]=bh;
    for (int i=bh;!isroot(i);i=pre[i]) q[++top]=pre[i];
    while (top) push(q[top--]);

    for (int fa;!(isroot(bh));rotate(bh))
        if (!isroot(fa=pre[bh]))
            rotate(get(fa)==get(bh)? fa:bh);
}

void expose(int x) {
    int t=0;
    while (x) {
        splay(x);
        ch[x][1]=t;
        update(x);           //产生了新儿子,需要update 
        t=x;
        x=pre[x];
    }
}

void makeroot(int x) {
    expose(x);
    splay(x);
    rev[x]^=1;
}

void link(int x,int y) {
    makeroot(x);
    pre[x]=y;
}

void cut(int x,int y) {
    makeroot(x);
    expose(y);
    splay(y);
    ch[y][0]=pre[x]=0;
    //update(y)
}

int find(int x) {
    expose(x);
    splay(x);
    while (ch[x][0]) x=ch[x][0];
    return x;
}

bool linked(int x,int y) {
    return find(x)==find(y);
}

int ask_sum(int x,int y) {
    makeroot(x);
    expose(y); splay(y);
    return sum[y];
}

int ask_dis(int x,int y) {
    makeroot(x);
    expose(y); splay(y);
    return size[y];
}

void change_point(int x,int z) {
    makeroot(x);
    v[x]=z;
    update(x);
}

void cal(int x,int a,int b) {     //*a   +b
    if (!x) return;
    v[x]=v[x]*a+b;                //不要忘了维护单点值 
    sum[x]=sum[x]*a+size[x]*b;
    ad[x]=ad[x]*a+b;
    mul[x]=mul[x]*a;
}

//void push(int bh) {
//  if (!bh) return;
//  if (rev[bh]) {
//      ...
//  }
//  if (ad[bh]!=0||mul[bh]!=1) {
//      if (ch[bh][0]) cal(ch[bh][0],mul[bh],ad[bh]);
//      if (ch[bh][1]) cal(ch[bh][1],mul[bh],ad[bh]);
//  }
//  mul[bh]=1; ad[bh]=0;
//}

void add(int x,int y,int z) {
    makeroot(x);
    expose(y); splay(y);
    cal(y,1,z);
}

void multi(int x,int y,int z) {
    makeroot(x);
    expose(y); splay(y);
    cal(y,z,0);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值