Splay学习笔记

n(n<=500000)个数,要求维护区间加,区间查询

很简单,用线段树/树状数组随便写写就能过

n(n<=500000)个数,维护插入、删除、找区间第K大/小

用平衡树/set也能过

n(n<=500000)个数,维护区间翻转,区间查询,插入,删除

这个时候就需要用到伸展树(Splay)

splay是一种极(常)灵(数)活(大)的数据结构,它能够维护线段树的部分操作和平衡树的操作
它的美妙之处在于能够旋转,从而完成一系列操作
它的旋转和平衡树的只转一次不同,它可以进行双旋操作来保证较优秀的时间复杂度(但常数还是大)

双旋操作是基于单旋的
首先是单旋:

和平衡树相同

然后是双旋:

当p不是根节点,且x和p同为左孩子或右孩子时进行Zig-Zig操作。
当x和p同为左孩子时,依次将p和x右旋;
当x和p同为右孩子时,依次将p和x左旋。

当p不是根节点,且x和p不同为左孩子或右孩子时,进行Zig-Zag操作。
当p为左孩子,x为右孩子时,将x左旋后再右旋。
当p为右孩子,x为左孩子时,将x右旋后再左旋。

有了单旋和双旋就可以进行splay操作:

int get(int o){return tree[tree[o].fa].son[1]==o;}
void rotate(QAQ o){
    int fa=tree[o].fa,ff=tree[fa].fa;
    int pd=get(o);
    if(ff) tree[ff].son[get(fa)]=o;
    tree[o].fa=ff;
    tree[fa].son[pd]=tree[o].son[1-pd];
    tree[tree[o].son[1-pd]].fa=fa;
    tree[fa].fa=o;tree[o].son[1-pd]=fa;
    push_up(fa);push_up(o);
}

void splay(int o,int goal){
//goal是目标节点的父节点,为防止旋转之后导致节点更换带来的影响
    while(tree[o].fa!=goal){
        int fa=tree[o].fa,ff=tree[fa].fa;
        if(ff!=goal) if(tree[ff].son[1]==fa ^ tree[fa].son[1]==o) rotate(o);
                    else rotate(fa);
        rotate(o);
    }
    if(!goal) rot=o;//换根
    push_up(o);
}

进行区间操作[l,r]时,常常把节点l-1转到根,r+1转到根的右儿子,这样根的右儿子的左儿子就是操作区间 ,并且常常添加虚拟节点0和n+1来进行[1,n]的操作
区间翻转就直接交换左右儿子
区间加直接加lazy标记

还能查询某一个值的排名:找到节点,转到根,根的做儿子大小+1就是排名

注意:splay中有些操作不能同时进行,例如求第K大和区间操作就不能同时进行,因为前一种操作改变了原序列顺序,后一个操作要求不能改变,这样就发生冲突。splay旋转仅仅是改变树的形状,并没有改变原序列顺序(树的中序遍历)

原文地址:https://www.cnblogs.com/heower/p/8463547.html