神奇的splay树

总结

  1. splay树是一种BST,其通过不断的splay操作维持树的平衡;其基本思想是将频率高的点(实际是每次查找的点)通过splay操作旋转到树根
  2. 核心操作:
  • update(x): 维护信息,类似线段树中的push_up

  • rotate(x): 单旋,即将x旋转到其父节点y的位置,需要注意顺序(替换y,x的子树加入y, y最为x的子树)

  • splay(int x,int s): 将x节点旋转到s下方。情况1:x,y,z共线,先rotate(y),再rotate(x); 情况2:不共线,rotate(x) 两次

  • find(int x): 找到后需旋转到根

  • insert(int x): 找到计数++,否则产生新节点并初始化

  • Next(int x,int f): 寻找前驱和后继

  • Delete(int x):先找前驱和后继,然后将前驱旋转到根节点root,后继旋转到root下面,然后删掉x(此时在t[nxt].ch[0])

  • kth(int x): 比较size即可

  • tarjan搞的算法都很难写,而且很容易写错呀,还不好调试吧

  • reference: blog1 blog2 blog3

模板题luogu3369

代码

#include <bits/stdc++.h>
using namespace std;
const int N=201000;
struct splay_tree
{
int ff,cnt,ch[2],val,size;
} t[N];
int root,tot;//root==0 表示是空树 根节点的ff为0
void update(int x)//更新节点x
{
t[x].size = t[t[x].ch[0]].size+t[t[x].ch[1]].size+t[x].cnt; }
void rotate(int x)//对x进行单旋
{
int y = t[x].ff; int z =t[y].ff;
int k = (t[y].ch[1]==x);
t[z].ch[t[z].ch[1]==y] = x;// 用x替换z节点的儿子节点y
t[x].ff = z;
t[y].ch[k] = t[x].ch[1^k]; //先把 x的子树移到y
t[t[x].ch[1^k]].ff = y;
t[x].ch[1^k] = y; //y-x 与 x-y关系相反,构建x-y关系
t[y].ff = x;
update(y);update(x);// 先更新下面的层 }
void splay(int x,int s)//将x 旋转到 s下方, s==0 则是旋转到根
{
while(t[x].ff!=s)
{
int y=t[x].ff,z=t[y].ff;
if (z!=s)//z==s 意味只需旋转一下x即可
(t[z].ch[0]==y)^(t[y].ch[0]==x)?rotate(x):rotate(y);//如果 x,z,y同线,先旋转y,再旋转x;否则旋转两次x
rotate(x);
}
if (s==0) //s==0 x旋转到根,更新root
root=x;
}
void find(int x)
{
int u=root;
if (!u)
return ;//空树
while(t[u].ch[x>t[u].val] && x!=t[u].val)//x>t[u].val 向右找, x< t[u].val 向左找
u=t[u].ch[x>t[u].val];
//也有可能找不到
splay(u,0);//找到x,将其splay到根
}
void insert(int x) //插入操作
{
int u=root,ff=0;
while(u && t[u].val!=x)
{
ff=u;
u=t[u].ch[x>t[u].val];
}
if (u)//找到元素x的节点,计数++
t[u].cnt++;
else//没有找到则产生新节点
{
u=++tot;
if (ff)//ff!=0 u不是根节点
t[ff].ch[x>t[ff].val]=u;
t[u].ch[0]=t[u].ch[1]=0;//初始化t[u]
t[tot].ff=ff;
t[tot].val=x;
t[tot].cnt=1;
t[tot].size=1;
}
splay(u,0);//u splay到根节点
}
int Next(int x,int f)//f=0 表示前驱 f=1表示后继
{
find(x);// 如果找到x所在节点 会被splay到根节点
int u=root;
if (t[u].val>x && f) //find没有找到x
return u;
if (t[u].val<x && !f)
return u;
//find 找打了x,且此时再根节点上
u=t[u].ch[f];
while(t[u].ch[f^1])//左子树的最右边节点/右子树的最左边节点
u=t[u].ch[f^1];
return u;
}
void Delete(int x)
{
int last=Next(x,0);
int Net=Next(x,1);
splay(last,0);
splay(Net,last); //找到前驱和后继并将前驱splay到根节点,后继splay到根节点下面; 则x代表的节点在是根节点的左儿子
int del=t[Net].ch[0];
if (t[del].cnt>1)//计数--
{
t[del].cnt--;
splay(del,0);
}
else
t[Net].ch[0]=0;//彻底删掉
}
int kth(int x)
{
int u=root;
while(t[u].size<x)//不存在排名为x
return 0;
while(1)
{
int y=t[u].ch[0];
if (x>t[y].size+t[u].cnt) //在右子树
{
x-=t[y].size+t[u].cnt;
u=t[u].ch[1];
}
else if (t[y].size>=x)//在左子树
u=y;
else //就在u
return t[u].val;
}
} /*
插入数值x。
删除数值x(若有多个相同的数,应只删除一个)。
查询数值x的排名(若有多个相同的数,应输出最小的排名)。
查询排名为x的数值。
求数值x的前驱(前驱定义为小于x的最大的数)。
求数值x的后继(后继定义为大于x的最小的数)。
*/
int main()
{
int n;
scanf("%d",&n);
insert(1e9); //始终保持能够找到前驱和后继
insert(-1e9);
while(n--)
{
int opt,x;
scanf("%d%d",&opt,&x);
if (opt==1)
insert(x);
if (opt==2)
Delete(x);
if (opt==3)
{
find(x);
printf("%d\n",t[t[root].ch[0]].size);
}
if (opt==4)
printf("%d\n",kth(x+1));
if (opt==5)
printf("%d\n",t[Next(x,0)].val);
if (opt==6)
printf("%d\n",t[Next(x,1)].val);
}
return 0;
}

神奇的splay树的更多相关文章

  1. Splay树-Codevs 1296 营业额统计

    Codevs 1296 营业额统计 题目描述 Description Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况. Tiger拿出了公司 ...

  2. ZOJ3765 Lights Splay树

    非常裸的一棵Splay树,需要询问的是区间gcd,但是区间上每个数分成了两种状态,做的时候分别存在val[2]的数组里就好.区间gcd的时候基本上不支持区间的操作了吧..不然你一个区间里加一个数gcd ...

  3. Splay树再学习

    队友最近可能在学Splay,然后让我敲下HDU1754的题,其实是很裸的一个线段树,不过用下Splay也无妨,他说他双旋超时,单旋过了,所以我就敲来看下.但是之前写的那个Splay越发的觉得不能看,所 ...

  4. 暑假学习日记:Splay树

    从昨天开始我就想学这个伸展树了,今天花了一个上午2个多小时加下午2个多小时,学习了一下伸展树(Splay树),学习的时候主要是看别人博客啦~发现下面这个博客挺不错的http://zakir.is-pr ...

  5. 1439. Battle with You-Know-Who(splay树)

    1439 路漫漫其修远兮~ 手抄一枚splay树 长长的模版.. 关于spaly树的讲解   网上很多随手贴一篇 貌似这题可以用什么bst啦 堆啦 平衡树啦 等等 这些本质都是有共同点的 查找.删除特 ...

  6. 伸展树(Splay树)的简要操作

    伸展树(splay树),是二叉排序树的一种.[两个月之前写过,今天突然想写个博客...] 伸展树和一般的二叉排序树不同的是,在每次执行完插入.查询.删除等操作后,都会自动平衡这棵树.(说是自动,也就是 ...

  7. [Splay伸展树]splay树入门级教程

    首先声明,本教程的对象是完全没有接触过splay的OIer,大牛请右上角.. 首先引入一下splay的概念,他的中文名是伸展树,意思差不多就是可以随意翻转的二叉树 PS:百度百科中伸展树读作:BoGa ...

  8. hdu 3436 splay树+离散化*

    Queue-jumpers Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) To ...

  9. hdu 1890 splay树

    Robotic Sort Time Limit: 6000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Tot ...

随机推荐

  1. [转]十五天精通WCF——终结篇 那些你需要注意的坑

    终于一路走来,到了本系列的最后一篇了,这一篇也没什么好说的,整体知识框架已经在前面的系列文章中讲完了,wcf的配置众多,如果 不加一些指定配置,你可能会遇到一些灾难性的后果,快来一睹为快吧. 一: 第 ...

  2. fixed_date , 赋权技巧 ,procedure执行方式, PL/SQL注意的地方

    本文讨论4个知识点, 1. fixed_date参数 2. 赋权技巧 3. procedure执行的方式 4. PL/SQL中要注意的几个地方 fixed_date参数 客户想修改oracle的 系统 ...

  3. WPF中控件TextBlock使用(简单)

    TextBlock主要用来显示文字.比方: <TextBlock Name="txtBlockOutpuMessage"   Text="hello" / ...

  4. IE6、IE7的兼容问题

    通常,网页的兼容问题,就是IE6\IE7的问题.表现为错位.换行,不支持CSS3等. 而其中,错位.换行,原因往往在于没有指明元素的width.height. 一般银瓦不告诉他.

  5. C# 正则表达式 和 JAVA表达式是想通的

    正则表达式语法 也许有人会说,现在需要正则表达式去验证什么的话,直接在网上找不久一大片吗?还需要学什么啊! 是的,现在在网上找确实是一找一大片,但是,有时候我们也遇到这样的情况,就是我们在网上找的复制 ...

  6. 电脑升级win10后visio的问题

    上周由于电脑意外蓝屏,系统从win7升级到了win10,昨天工作写文档时才发现缺少画图的工具,于是按照了visio2013,在编辑设计图时发现,一旦用visio打开或编辑图后再到word里设计图的内容 ...

  7. 14招搞定JavaScript调试

    14招搞定JavaScript调试 译者按: 很多时候,大家可能只是依靠console.log来调试JavaScript代码,这样做的局限性不言而喻,这篇博客将教你几招实用的调试技巧. 原文: The ...

  8. [Apple开发者帐户帮助]三、创建证书(5)创建WatchKit服务证书

    WatchKit服务证书允许您使用Apple推送通知(APN)将更新推送到Apple Watch上的复杂功能. 所需角色:帐户持有人或管理员. 在证书,标识符和配置文件中,从左侧的弹出菜单中选择iOS ...

  9. weui&flexible布局

    1.weui 一开始以为只能用于小程序中,原来分两种:weui-wxss-master和weui-master.真的是强大的不得了,把设计好的样式和功能封装.然后分类,有明确的层级和逻辑,感动!!值得 ...

  10. git的常用命令。。

    git的常用命令.. git help <command>  显示command的help git show  显示某次提交的内容 git show $id git co -- <f ...