平衡树+LCT全纪录

平衡树(splay)

平衡数模板

平衡树能干些什么呢?

  • 插入一个数

  • 删除一个数

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

  • 查询排名为 x 的数

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

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

  • 区间翻转

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

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

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

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

  • 插入一个数

  • 删除一个数

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

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

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

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

  • 区间翻转(没写过)

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

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

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

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

如果要加上 r e v e r
我们先调用 f i n d _ p m 夹逼我们需要翻转的区间
之后再这个区间打上翻转标记即可
不过,这样我们就需要在 s p l a y 之前 d o w n 一下
每次 f i n d 的时候也需要 p u s h (多 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;
}

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维护子树信息(难)

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

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

e x p o s e

访问一个结点,该结点到根的路径就会变成偏爱路径,在一棵 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];
    }
}

m a k e r o o t

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

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

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

l i n k ( x , y )

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

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

c u t ( x , y )

和link一样,我们需要把 x 变成树的根:makeroot(x)
之后 e x p o s e ( y ) , s p l a y ( y )
我们就得到了一棵以 y 为根, x < > y 路径的 s p l a y
x 是根节点,ta的深度最小, y 是辅助树的根节点
所以 x 一定是 y 的左儿子
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)
}

f i n d

这个操作可以找到 x 结点在原树中的根结点
(根结点的深度最小,所以在 s p l a 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);
}

路径权值和

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

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

节点到根的距离:

同理,返回 s i z e 即可

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

更改节点值

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

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

更改路径值

这个操作比较厉害,可以处理路径加乘的问题
我们像线段树的加乘那样每个结点维护加标记和乘标记
每次在push的时候,维护值以及标记
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);
}
发布了941 篇原创文章 · 获赞 192 · 访问量 32万+

猜你喜欢

转载自blog.csdn.net/wu_tongtong/article/details/79779390
今日推荐