Splay学习总结


基本思路

Splay,即伸展树,以其鬼魅的伸展操作而得名。核心操作是通过旋转来把某个节点转到根节点,并通过旋转操作改变树的结构来保证复杂度均摊O(log n)。

实现步骤

本人模板风格大多来源于白书+自己修改
(然而机房很多童鞋的代码都是%H,找不到同好啊,so sad)


基本操作

  • Update(更新)
inline void Update(int k){
    tr[k].size=tr[tr[k].ch[0]].size+tr[tr[k].ch[1]].sz+tr[k].s;
}
  • Rotate(旋转)
void Rotate(int &k,int d){ //d=0为右旋,d=1为左旋
    int t=tr[k].ch[d^1];
    tr[k].ch[d^1]=tr[t].ch[d]; tr[t].ch[d]=k;
    Update(k); Update(t); k=t;
}   

维护离散元素

  • Splay(伸展)
void Splay(int &k,int x){//用于在树中找到元素x并提到根节点
    if(x!=tr[k].key){
        int d1=(x<tr[k].key?0:1),t=tr[k].ch[d1];
        int d2=(x<tr[t].key?0:1);
        if(t && x!=tr[t].key){ //双旋操作
            Splay(tr[t].ch[d2],x); 
            if(d1==d2) Rotate(k,d1^1);
            else Rotate(tr[k].ch[d1],d1);
        }
        Rotate(k,d1^1);
    }
}
  • Insert(插入)
void Insert(int &k,int x){
    if(!k){
        k=++cnt; tr[k].key=x; 
        tr[k].ch[0]=tr[k].ch[1]=0; tr[k].size=tr[k].s=1;
        Splay(root,x);
        return;
    }
    tr[k].sz++;
    if(tr[k].key==x){
        tr[k].s++; Splay(root,x);
        return; 
    }
    if(tr[k].key>x) Insert(tr[k].ch[0],x);
    else Insert(tr[k].ch[1],x);
}
  • Delete(删除)
    删除操作需要充分利用到Splay的伸展操作。先找到要删除的元素的前驱和后继,之后把前驱转到根节点,把后继转到根结点的右子节点,根据二叉树的性质,要删除的节点一定在其后继的左子树上且这棵树上就这一个节点,直接删除即可。
void Delete(int x){
    Splay(root,x);
    if(tr[root].s>1){
        tr[root].s--; tr[root].sz--; 
        return; 
    }
    if(!(tr[root].ch[0]*tr[root].ch[1])){
        root=tr[root].ch[0]^tr[root].ch[1];
        return; 
    }
    int pre=0,suf=0;
    find_pre(root,x,pre); find_suf(root,x,suf);
    Splay(root,pre); Splay(tr[root].ch[1],suf);
    int pos=tr[tr[root].ch[1]].ch[0];
    tr[pos].sz=tr[pos].s=tr[pos].ch[0]=tr[pos].ch[1]=tr[pos].key=0;
    tr[tr[root].ch[1]].ch[0]=0; 
    Update(tr[root].ch[1]); Update(root);
}

维护序列

对于维护序列,我们需要在序列的开头和结尾引进两个虚点,来方便我们在序列开头和结尾进行插入和删除等操作。

  • Splay(伸展)
void Splay(int &k,int x){//用于找到树中第x大的元素并提到树根,一般适用于维护序列的问题,即找到序列中处在第x位的元素
//  Pushdown(k);
//  if(tr[k].ch[0]) Pushdown(tr[k].ch[0]);
//  if(tr[k].ch[1]) Pushdown(tr[k].ch[1]);
    int d1=(tr[tr[k].ch[0]].sz<x?1:0),t=tr[k].ch[d1];
    if(d1==1) x-=tr[tr[k].ch[0]].sz+1;
    if(x){
        int d2=(tr[tr[t].ch[0]].sz<x?1:0);
        if(d2==1) x-=tr[tr[t].ch[0]].sz+1;
        if(x){
            Splay(tr[t].ch[d2],x);
            if(d1==d2) Rotate(k,d1^1);
            else Rotate(tr[k].ch[d1],d1);
        }
        Rotate(k,d1^1);
    }
}
  • Insert(插入)
    当要在序列的pos位置插入元素(新的序列)时,可以将序列第x位的节点转至根,第x+1位的节点转至根的右子节点,然后直接插入到x+1位的节点的左子节点即可。
    (由于一开始引进了虚点,所以我们需要将x+1位的节点转至根,第x+2位的节点转至根的右子节点)
void Insert(int x,char val){
    Splay(root,x+1); Splay(tr[root].ch[1],x+1-tr[tr[root].ch[0]].sz);
    //本应为Splay(tr[root].r,x+2-(tr[tr[root].ch[0]].sz+1));
    int k=++cnt;
    tr[k].key=val; tr[k].ch[0]=tr[k].ch[1]=0;
    tr[tr[root].ch[1]].ch[0]=k;
    Update(k); Update(tr[root].ch[1]); Update(root);
}
  • Delete(删除)
    类似插入,当删除序列[l,r]段的元素时,将序列第l-1位的元素转至根,第r+1位的元素转至根的右子节点,然后删除其左子节点。为了节省空间,一般我们需要将这些被删除的元素记录到一个循环队列中,重复使用这些被删除的编号。
    (由于之前引进的虚点,我们需要操作的是第l位和第r+2位的节点,下同)
void Delete(int l,int r){
    Splay(root,l); Splay(tr[root].ch[1],r+1-tr[tr[root].ch[0]].sz);
    tr[tr[root].ch[1]].ch[0]=0;
    Update(tr[root].ch[1]); Update(root);
}
  • Reverse(翻转)
    要实现对[l,r]的翻转操作,可以将[l,r]对应的子树中所有节点的左右子节点交换位置,这样即可使树的中序遍历翻转。类似于线段树,我们可以使用对节点的标记和下传来完成对这棵子树的翻转。
void Reverse(int l,int r){
    Splay(root,l); Splay(tr[root].ch[1],r+1-tr[tr[root].ch[0]].sz);
    tr[tr[tr[root].ch[1]].ch[0]].flip^=1;
    Pushdown(tr[tr[root].ch[1]].ch[0]);
}
  • Pushdown(下传)
void Pushdown(int k){
    if(tr[k].flip){ //序列翻转标记下传
        tr[k].flip=0;
        swap(tr[k].ch[0],tr[k].ch[1]);
        tr[tr[k].ch[0]].flip^=1;
        tr[tr[k].ch[1]].flip^=1;    
    }
}

用途&优缺点

用途:实现一系列的序列操作,维护数据什么的……
优点:快
缺点:代码长容易打挂

推荐题目

BZOJ 1014 3223 3224 1251 1500 1503 1507 3506

可持久化splay是一种数据结构,它是对splay树进行修改和查询的一种扩展。在传统的splay树中,对树的修改操作会破坏原有的树结构,而可持久化splay树则允许我们对树进行修改、查询,并且可以保存修改后的每个版本的树结构。 在可持久化splay树中,我们不会直接对原树进行修改,而是通过复制每个节点来创建新的版本。这样,每个版本都可以独立地修改和查询,保留了原有版本的结构和状态。每个节点保存了其左子树和右子树的引用,使得可以在不破坏原有版本的情况下进行修改和查询。 为了实现可持久化splay树,我们可以使用一些技巧,比如引用中提到的哨兵节点和假的父节点和孩子节点。这些技巧可以帮助我们处理根节点的旋转和其他操作。 此外,可持久化splay树还可以与其他数据结构相结合,比如引用中提到的可持久化线段树。这种结合可以帮助我们解决更复杂的问题,比如区间修改和区间查询等。 对于可持久化splay树的学习过程,可以按照以下步骤进行: 1. 理解splay树的基本原理和操作,包括旋转、插入、删除和查找等。 2. 学习如何构建可持久化splay树,包括复制节点、更新版本和保存历史版本等。 3. 掌握可持久化splay树的常见应用场景,比如区间修改和区间查询等。 4. 深入了解与可持久化splay树相关的其他数据结构和算法,比如可持久化线段树等。 在解决问题时,可以使用二分法来确定答案,一般称为二分答案。通过对答案进行二分,然后对每个答案进行检查,以确定最终的结果。这种方法可以应用于很多问题,比如引用中提到的在线询问问题。 综上所述,可持久化splay是一种对splay树进行修改和查询的扩展,可以通过复制节点来创建新的版本,并且可以与其他数据结构相结合解决更复杂的问题。学习过程中可以按照一定的步骤进行,并且可以使用二分法来解决一些特定的问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [[学习笔记]FHQ-Treap及其可持久化](https://blog.csdn.net/weixin_34283445/article/details/93207491)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [可持久化数据结构学习笔记](https://blog.csdn.net/weixin_30376083/article/details/99902410)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值