秦同学的详细版本

splay就是一种将树上的一个节点经过旋转到节点的一种操作,来保持树的平衡

splay本质:二叉搜索树

特点:结点x的左子树权值都小于x的权值,右子树权值都大于x的权值

  1. 如果当前处于共线状态的话,那么先旋转y,再旋转x.这样可以强行让他们不处于共线状态,然后平衡这棵树.
  2. 如果当前不是共线状态的话,那么只要旋转x即可.

普通平衡树
基本上每个平台都有的模板题。

为什么要splay?仅仅是插入不是插进去就行吗?成不成为根节点有什么关系?

这是为了查找比x小/大的第一个数做铺垫,因为有可能x在树中没有出现过,所以先插入x,再找前驱/后继,这就可以直接从根节点找起)

splay的旋转操作

void pushup(int u)
{tr[u].size=tr[tr[u].s[1]].size+tr[tr[u].s[0]].size+tr[u].cnt;
}void rotate(int x)
{int y=tr[x].p,z=tr[y].p;int k=tr[y].s[1]==x;tr[z].s[tr[z].s[1]==y]=x,tr[x].p=z;//第一条线tr[y].s[k]=tr[x].s[k^1],tr[tr[x].s[k^1]].p=y;//第三条线tr[x].s[k^1]=y,tr[y].p=x;//第2条线pushup(y),pushup(x);
}

splay操作

void splay(int x,int goal)//将x旋转为goal的儿子,如果goal是0则旋转到根
{while(t[x].ff!=goal)//一直旋转到x成为goal的儿子{int y=t[x].ff,z=t[y].ff;//父节点祖父节点if(z!=goal)//如果Y不是根节点,则分为上面两类来旋转(t[z].ch[0]==y)^(t[y].ch[0]==x)?rotate(x):rotate(y);//判断共线还是不共线//这就是之前对于x和y是哪个儿子的讨论rotate(x);//无论怎么样最后的一个操作都是旋转x}if(goal==0)root=x;//如果goal是0,则将根节点更新为x
}

查找find操作

从根节点开始,左侧都比他小,右侧都比他大,
所以只需要相应的往左/右递归
如果当前位置的val已经是要查找的数
那么直接把他Splay到根节点,方便接下来的操作

void find(int x)//查找x的位置,并将其旋转到根节点
{int u=root;if(!u) return ;//树空while(tr[u].s[x>tr[u].v]&&x!=tr[u].v)//当存在儿子并且当前位置的值不等于xu=tr[u].s[x>tr[u].v];//跳转到儿子,查找x的父节点splay(u,0);//把当前位置旋转到根节点
}

Insert操作

往Splay中插入一个数
类似于Find操作,只是如果是已经存在的数,就可以直接在查找到的节点的进行计数
如果不存在,在递归的查找过程中,会找到他的父节点的位置,
然后就会发现底下没有啦。。。
所以这个时候新建一个节点就可以了

inline void insert(int x)//插入x
{int u=root,ff=0;//当前位置u,u的父节点ffwhile(u&&t[u].val!=x)//当u存在并且没有移动到当前的值{ff=u;//向下u的儿子,父节点变为uu=t[u].ch[x>t[u].val];//大于当前位置则向右找,否则向左找}if(u)//存在这个值的位置t[u].cnt++;//增加一个数else//不存在这个数字,要新建一个节点来存放{u=++tot;//新节点的位置if(ff)//如果父节点非根t[ff].ch[x>t[ff].val]=u;t[u].ch[0]=t[u].ch[1]=0;//不存在儿子t[tot].ff=ff;//父节点t[tot].val=x;//值t[tot].cnt=1;//数量t[tot].size=1;//大小}splay(u,0);//把当前位置移到根,保证结构的平衡
}

前驱/后继操作Next

首先就要执行Find操作
把要查找的数弄到根节点
然后,以前驱为例
先确定前驱比他小,所以在左子树上
然后他的前驱是左子树中最大的值
所以一直跳右结点,直到没有为止
找后继反过来就行了

int Next(int x,int f)//查找x的前驱(0)或者后继(1)
{find(x);int u=root;//根节点,此时x的父节点(存在的话)就是根节点if(t[u].val>x&&f)return u;//如果当前节点的值大于x并且要查找的是后继if(t[u].val<x&&!f)return u;//如果当前节点的值小于x并且要查找的是前驱u=t[u].ch[f];//查找后继的话在右儿子上找,前驱在左儿子上找while(t[u].ch[f^1])u=t[u].ch[f^1];//要反着跳转,否则会越来越大(越来越小)return u;//返回位置
}

删除操作

现在就很简单啦
首先找到这个数的前驱,把他Splay到根节点
然后找到这个数后继,把他旋转到前驱的底下
比前驱大的数是后继,在右子树
比后继小的且比前驱大的有且仅有当前数
在后继的左子树上面,
因此直接把当前根节点的右儿子的左儿子删掉就可以啦

void dele(int x)
{int last=Next(x,0);//找到前驱int next=Next(x,1);//找的后继splay(last,0),splay(next,last);//将前驱旋转到根节点,后继旋转到根节点下面//很明显,此时后继是前驱的右儿子,x是后继的左儿子,并且x是叶子节点int del=tr[next].s[0];//后继的左儿子if(tr[del].cnt>1)//如果超过一个{tr[del].cnt--;splay(del,0);}else tr[next].s[0]=0;//这个节点直接丢掉(不存在了)
}

查找第K大

从当前根节点开始,检查左子树大小
因为所有比当前位置小的数都在左侧
如果左侧的数的个数多余K,则证明第K大在左子树中
否则,向右子树找,找K-左子树大小-当前位置的数的个数
记住特判K恰好在当前位置

int  kth(int x)//查找排名为x的数
{int u=root;if(tr[u].size<x)//如果当前树上没有这么多数return 0;//不纯在while(true){int y=tr[u].s[0];//左儿子if(x>tr[y].size+tr[u].cnt){//如果排名比左儿子的大小和当前节点的数量要大x-=tr[y].size+tr[u].cnt;u=tr[u].s[1];}else if(tr[y].size>=x)//否则的话在当前节点或者左儿子上查找u=y;else //否则就是在当前根节点上return tr[u].v;}
}

完整代码

#include<bits/stdc++.h>
using namespace std;
const int N=100010;
struct node
{int s[2];int p,v,cnt,size;
}tr[N*4];
int idx,n,m,root;void pushup(int u)
{tr[u].size=tr[tr[u].s[1]].size+tr[tr[u].s[0]].size+tr[u].cnt;
}void rotate(int x)
{int y=tr[x].p,z=tr[y].p;int k=tr[y].s[1]==x;tr[z].s[tr[z].s[1]==y]=x,tr[x].p=z;tr[y].s[k]=tr[x].s[k^1],tr[tr[x].s[k^1]].p=y;tr[x].s[k^1]=y,tr[y].p=x;pushup(y),pushup(x);
}void splay(int x,int k)
{while(tr[x].p!=k){int y=tr[x].p,z=tr[y].p;if(z!=k)if((tr[y].s[1]==x)^(tr[z].s[1]==y)) rotate(x);else rotate(y);rotate(x);}if(!k) root=x;
}void find(int x)
{int u=root;if(!u) return ;while(tr[u].s[x>tr[u].v]&&x!=tr[u].v)u=tr[u].s[x>tr[u].v];splay(u,0);
}void insert(int x)
{int u=root,p=0;while(u&&tr[u].v!=x)p=u,u=tr[u].s[x>tr[u].v];if(u) tr[u].cnt++;else{u=++idx;if(p) tr[p].s[x>tr[p].v]=u;tr[u].s[1]=tr[u].s[0]=0;tr[u].p=p;tr[u].size=1;tr[u].cnt=1;tr[u].v=x;}splay(u,0);
}int  kth(int x)//查找排名为x的数
{int u=root;if(tr[u].size<x)//如果当前树上没有这么多数return 0;//不纯在while(true){int y=tr[u].s[0];//左儿子if(x>tr[y].size+tr[u].cnt){//如果排名比左儿子的大小和当前节点的数量要大x-=tr[y].size+tr[u].cnt;u=tr[u].s[1];}else if(tr[y].size>=x)//否则的话在当前节点或者左儿子上查找u=y;else //否则就是在当前根节点上return tr[u].v;}
}int Next(int x,int f)
{find(x);int u=root;if(tr[u].v>x&&f) return u;if(tr[u].v<x&&!f) return u;u=tr[u].s[f];//查找后继的话在右儿子上找,前驱在左儿子上找while(tr[u].s[f^1])u=tr[u].s[f^1];//要反着跳转,否则会越来越大(越来越小)return u;
}void dele(int x)
{int last=Next(x,0);//找到前驱int next=Next(x,1);//找的后继splay(last,0),splay(next,last);//将前驱旋转到根节点,后继旋转到根节点下面//很明显,此时后继是前驱的右儿子,x是后继的左儿子,并且x是叶子节点int del=tr[next].s[0];//后继的左儿子if(tr[del].cnt>1)//如果超过一个{tr[del].cnt--;splay(del,0);}else tr[next].s[0]=0;//这个节点直接丢掉(不存在了)
}int main()
{cin>>n;insert(-1e9);insert(1e9);while(n--){int op,x;cin>>op>>x;if(op==1)insert(x);else if(op==2)dele(x);else if(op==3){find(x);printf("%d\n",tr[tr[root].s[0]].size);}else if(op==4){printf("%d\n",kth(x+1));}else  if (op==5)printf("%d\n",tr[Next(x,0)].v);if (op==6)printf("%d\n",tr[Next(x,1)].v);}return 0;
}

splay的一些操作相关推荐

  1. 【BZOJ1500】【codevs1758】维修数列,简析Splay的综合操作

    Time:2016.05.12 Author:xiaoyimi 转载注明出处谢谢 传送门1 传送门2 思路: 我的天啊! Splay大板子! 调了好久啊! 这里的Splay没有判断的key值,换一种说 ...

  2. Splay树各操作--数组

    伸展树删除元素非常方便.. View Code #include <stdio.h>#include <string.h>#include <algorithm># ...

  3. 二逼平衡树——树套树(线段树套Splay平衡树)

    题面 Bzoj3196 解析 线段树和Splay两棵树套在一起,常数直逼inf,但最终侥幸过了 思路还是比较简单, 在原数组维护一个下标线段树,再在每一个线段树节点,维护一个对应区间的权值Splay. ...

  4. Splay ---- 文艺平衡树区间翻转的建树模式

    Splay 的区间操作 这个splay已经不是平常的权值splay了, 每个节点维护区间的位置取决于它在树里面的位置 首先这课splay中序遍历的结果就是你维护的整个区间的数值!!注意这个中序遍历很重 ...

  5. BZOJ 1208 宠物饲养所 Splay

    要实现的操作是插入,删除,找到比指定值大的,小的值操作. Splay的删除操作可以是直接用二叉搜索树的删除方式,或者是先将要删除的节点Splay到根,然后找到左子树中最大的节点,将其Splay到根的左 ...

  6. ●HDU 2871 Memory Control(Splay)

    ●赘述题目 四种操作: ○Reset:将整个内存序列清空. ○New a:在尽量靠左的位置新建一个长度为a的内存块,并输出改内存块起始位置.(各个内存块即使相邻也不会合并..) ○Free a:将a点 ...

  7. 文艺平衡树 Splay 学习笔记(1)

    (这里是Splay基础操作,reserve什么的会在下一篇里面讲) 好久之前就说要学Splay了,结果苟到现在才学习. 可能是最近良心发现自己实在太弱了,听数学又听不懂只好多学点不要脑子的数据结构. ...

  8. 洛谷P2497:基站建设(splay、斜率优化)

    所谓splay斜率优化dp,就是利用splay和斜率对dp进行优化 (逃) 解析 在斜优的时候,有时我们会发现我们插入的点的横坐标并不单调 这个时候我们就无法利用单调队列维护凸包了 这时,我们就要请出 ...

  9. 【BZOJ1014】【tyvj3486】火星人prefix,Splay+字符串hash

    Time:2016.07.19 Author:xiaoyimi 转载注明出处谢谢 传送门1 传送门2 思路&&注意: LCP这个东西可以用后缀数组,扩展kmp什么的来做 这里加上了插入 ...

最新文章

  1. Silverlight与JavaScript通信
  2. 关于SQL的几个备忘
  3. BITPOS key bit [start] [end]
  4. go使用for...range遍历数组
  5. jQuery经典案例【倒计时】
  6. scapy 安装及简单测试
  7. Linux文件系统之df
  8. PowerDesigner(二)-项目和框架矩阵
  9. java 抽象接口_JAVA中的“抽象接口”
  10. Python字典对象实现原理
  11. 使用go语言开发一个后端gin框架的web项目
  12. 安阳工学院ACM历史(续)
  13. win7 升级到 win10
  14. c语言编写qq机器人软件,未编译的QQ机器人C语言版
  15. 新编计算机组装与维护标准教程,计算机组装与维护标准教程(2018-2020版)
  16. Sass-5【颜色函数、透明度函数、@规则】
  17. 2023电工杯数学建模B题思路
  18. 浏览器调用桌面程序方法
  19. OpenSSL BIO 自我扫盲
  20. Linux 获取磁盘唯一ID方式

热门文章

  1. python文件管理器_Tkinter 之文件管理器
  2. Android 编程_基础
  3. Unicode编码和Base64编码
  4. C语言练习,利用求阶乘函数Fact(),编程计算并输出从1到n之间所有数的阶乘值。
  5. CSS3选择器-属性选择器
  6. 如何让自己变得更加成熟
  7. python pip什么意思_python中的pip是什么意思
  8. 一种基于物理信息极限学习机的PDE求解方法
  9. 自动关闭MessageBox
  10. Windows留后门--教程(二)——Windows计划任务后门