【笔记】假·splay总结

15 篇文章 0 订阅
2 篇文章 0 订阅

splay小归纳

感觉splay就是一个根据中序遍历维护序列并提取信息的二叉树,根据旋转尽量维护二叉树的平衡并进行各类修改操作

splay应该有如下几个操作:

左右旋

作用是把当前节点提拉到它父节点的位置,并满足整棵splay的中序遍历不变

目的是将当前splay转换成等效的但更便于操作的splay

事实上左旋和右旋应该是不一样的,但由于打代码时分开打比较繁琐,直接合并也无伤大雅

代码(合并):

inline void rotate(int x){
    int fa=f[x],grand=f[f[x]],le=blood(x);

    f[x]=grand;
    if(grand)ch[grand][blood(fa)]=x;

    ch[fa][le]=ch[x][le^1];
    f[ch[fa][le]]=fa;

    f[fa]=x;
    ch[x][le^1]=fa;

    update(fa);update(x);
    return ;
}
splay

作用同左右旋

目的同左右旋

一次splay就是多个左右旋加在一起

代码:

inline void splay(int x){
    for(int fa;(fa=f[x]);rotate(x))
        if(f[f[x]])
            rotate(blood(x)==blood(f[x])?f[x]:x);
    root=x;
    return ;
}
查找

作用是找到当前第k项或着是权值为k的元素的编号

目的一般是满足题目的查找操作,或是辅助其他操作

代码(查找第k项):

int find(int x){
    //del(low-ex);
    int now=root;
    while(1){
        if(ch[now][0]&&x<=size[ch[now][0]]){now=ch[now][0];continue;}
        if(ch[now][0])x-=size[ch[now][0]];
        x-=tot[now];
        if(x<=0) return data[now];
        now=ch[now][1];
    }
    puts("ERROR IN FIND");
    return -32767;
}

代码(查找权值为k的元素编号,并提至根节点):

inline void find(int x){
    if(!root)return ;
    int now=root;
    while(tr[now].ch[x>tr[now].val]&&x!=tr[now].val)now=tr[now].ch[x>tr[now].val];
    splay(now,0);
    return ;
}
求前驱&后驱

作用是找到当前节点在序列上的(前/后)一个节点

目的很随意,这个操作可以搞很多事

代码:

inline int nxt(int x,int leaf){
    splay(x,0);
    find(x);
    int now=root;
    if(leaf&&tr[now].val>x)return now;
    if(!leaf&&tr[now].val<x)return now;
    now=tr[now].ch[leaf];
    while(tr[now].ch[leaf^1])now=tr[now].ch[leaf^1];
    return now;
}
还有很多组合操作,如:

插入节点

查找+建立父子关系

代码

inline void insert(int x){
    int now=root,f=0;
    while(now&&tr[now].val!=x){
        f=now;
        now=tr[now].ch[x>tr[now].val];
    }
    if(now)++tr[now].cnt;
    else{
        now=++sz;
        if(f)tr[f].ch[x>tr[f].val]=now;
        tr[now].ch[0]=tr[now].ch[1]=0;
        tr[now].f=f,tr[now].val=x;
        tr[now].cnt=1;tr[now].size=1;
    }
    splay(now,0);
    return ;
}

删除节点

查找+splay+解除父子关系

代码

inline void del(int x){
    int fr=nxt(x,0),Ba=nxt(x,1);
    splay(fr,0);splay(Ba,fr);
    if(tr[tr[Ba].ch[0]].cnt>1){
        --tr[tr[Ba].ch[0]].cnt;
        splay(tr[Ba].ch[0],0);
    }
    else
        tr[Ba].ch[0]=0;
    return ;
}
还有一些附加操作,如:

区间操作

主体思想就是打lazy标识
然而为了找到操作区间[l,r],需要找到l的前驱和r的后驱,将l-1转到根节点,将r+1结点转到根节点的右儿子,则以根节点的右儿子的左儿子为根的子树即为目标操作区间,易证

代码(以文艺平衡树为例):

#include<bits/stdc++.h>
using namespace std;
#define blood(x) (ch[f[(x)]][1]==(x))
#define cl(x) memset(x,0,sizeof(x))
#define cl1(x) memset(x,-1,sizeof(x))
#define rg register
#define oo 0x3f3f3f3f
#define inf 0x7fffffff

template <typename _Tp> inline void read(_Tp&x){
    char c11=getchar();x=0;bool booo=0;
    while(c11!='-'&&!isdigit(c11))c11=getchar();if(c11=='-'){c11=getchar();booo=1;}
    while(isdigit(c11)){x=x*10+c11-'0';c11=getchar();}if(booo)x=-x;return ;
}

const int N=105000;
int ch[N][2],f[N],delta[N],size[N],key[N],a[N];
int sz=0,root=0;
int n,m;

inline void update(int x){size[x]=size[ch[x][0]]+size[ch[x][1]]+1;}

inline void pushdown(int x){
    if(x&&delta[x]){
        delta[ch[x][0]]^=1;
        delta[ch[x][1]]^=1;
        swap(ch[x][0],ch[x][1]);
        delta[x]=0;
    }
    return ;
}

inline int build(int,int,int);

inline int find(int);

inline void print(int now);

inline void rotate(int x){
    pushdown(f[x]);
    pushdown(x);
    int daddy=f[x],grand=f[f[x]],cho=blood(x);

    ch[daddy][cho]=ch[x][cho^1];
    f[ch[daddy][cho]]=daddy;

    ch[x][cho^1]=daddy;
    f[daddy]=x;

    f[x]=grand;
    if(grand)
        ch[grand][ch[grand][1]==daddy]=x;

    update(daddy);
    update(x);
    return ;
}

inline void splay(int x,int target){
    for(rg int fa;(fa=f[x])!=target;rotate(x))
        if(f[fa]!=target)
            rotate(blood(x)==blood(fa)?fa:x);
    if(target==0)root=x;
    return ;
}

int main(){
    freopen("in","r",stdin);
    read(n);read(m);
    a[1]=-oo,a[n+2]=+oo;
    for(rg int i=1;i<=n;++i)a[i+1]=i;
    root=build(1,n+2,0);
    int x,y;
    while(m--){
        read(x);read(y);
        int aa=find(x),bb=find(y+2);
        splay(aa,0);splay(bb,aa);
        delta[ch[ch[root][1]][0]]^=1;
    }
    print(root);
    return 0;
}

inline int find(int x){
    int now=root;
    while("%%% boshi %%%"){
        pushdown(now);
        if(x<=size[ch[now][0]])
            now=ch[now][0];
        else{
            x-=size[ch[now][0]]+1;
            if(!x)return now;
            now=ch[now][1];
        }
    }
    return -32767;
}

inline int build(int l,int r,int fa){
    if(l>r)return 0;
    int mid=(l+r)>>1;
    int now=++sz;
    key[now]=a[mid],f[now]=fa,delta[now]=0;
    ch[now][0]=build(l,mid-1,now);
    ch[now][1]=build(mid+1,r,now);
    update(now);
    return now;
}

inline void print(int now){
    pushdown(now);
    if(ch[now][0])print(ch[now][0]);
    if(key[now]!=-oo&&key[now]!=+oo)
    printf("%d ",key[now]);
    if(ch[now][1])print(ch[now][1]);
    return ;
}

2017年最后一天,祝各位神犇在新的一年里拿省一,进省队,拿Au,签清北

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值