前言

上一节中,我们讲述了Splay的核心操作rotate与splay

本节我会教大家如何用这两个函数实现各种强大的功能

为了方便讲解,我们拿这道题做例题来慢慢分析

利用splay实现各种功能

首先,我们需要定义一些东西

各种指针

    struct node
{
int v;//权值
int fa;//父亲节点
int ch[2];//0代表左儿子,1代表右儿子
int rec;//这个权值的节点出现的次数
int sum;//子节点的数量
};
int tot;//tot表示不算重复的有多少节点

rotate

splay

这两个函数就不讲了,前面已经讲的挺详细了

插入

根据前面讲的,我们在插入一个数之后,需要将其旋转到根

首先

当这棵树已经没有节点的时候,我们直接新建一个节点就好

inline int newpoint(int v,int fa)//v:权值;fa:它的爸爸是谁
{
tree[++tot].fa=fa;
tree[tot].v=v;
tree[tot].sum=tree[tot].rec=1;
return tot;
}

当这可树有节点的时候,我们根据二叉查找树的性质,不断向下走,直到找到一个可以插入的点,注意在走的时候需要更新一个每个节点的sum值

void Insert(int x)
{
int now=root;
if(root==0) {newnode(x,0);root=tot;}//
else
{
while(1)
{
T[now].sum++;
if(T[now].val==x) {T[now].rec++;splay(now,root);return ;}
int nxt=x<T[now].val?0:1;
if(!T[now].ch[nxt])
{
int p=newnode(x,now);
T[now].ch[nxt]=p;
splay(p,root);return ;
}
now=T[now].ch[nxt];
}
}
}

删除

删除的功能是:删除权值为v的节点

我们不难想到:我们可以先找到他的位置,再把这个节点删掉

int find(int v)
{
int now=root;
while(1)
{
if(tree[now].v==v) {splay(now,root);return now;}
int nxt=v<tree[now].v?0:1;
if(!tree[now].ch[nxt])return 0;
now=tree[now].ch[nxt];
}
}

这个函数可以找到权值为v的节点的位置,比较好理解,注意别忘记把找到的节点splay到根

另外我们还需要一个彻底删除的函数

update:

这个函数其实是不需要的,因为后面的节点一定会覆盖前面的节点

inline void dele(int x)
{
tree[x].sum=tree[x].v=tree[x].rec=tree[x].fa=tree[x].ch[0]=tree[x].ch[1]=0;
if(x==tot) tot--;
}

接下来的任务就是怎么样才能保证删除节点后整棵树还满足二叉查找树的性质

注意:我们在查找完一个节点的时候已经将他旋转到根了,所以他左边一定都比他小,除此之外没有比他小的节点了(否则还要考虑他父亲比他小的情况)

那么此时会出现几种情况

  • 权值为v的节点已经出现过

    这时候直接把他的rec和sum减去1就好
  • 本节点没有左右儿子

    这样的话就成了一棵空树
  • 本节点没有左儿子

    直接把他的右儿子设置成根
  • 既有左儿子,又有右儿子

    在它的左儿子中找到最大的,旋转到根,把它的右儿子当做根(也就是它最大的左儿子)的右儿子

最后把这个节点删掉就好

void delet(int x)
{
int pos=find(x);
if(!pos) return ;
if(T[pos].rec>1) {T[pos].rec--,T[pos].sum--;return ;}
else
{
if(!T[pos].ch[0]&&!T[pos].ch[1]) {root=0;return ;}
else if(!T[pos].ch[0]) {root=T[pos].ch[1];T[root].fa=0;return ;}
else
{
int left=T[pos].ch[0];
while(T[left].ch[1]) left=T[left].ch[1];
splay(left,T[pos].ch[0]);
connect(T[pos].ch[1],left,1);
connect(left,0,1);//
update(left);
}
}
}

查询x数的排名

update in 2018.4.10

以前的自己too naive

这个函数这么写就好

int rak(int val)
{
int pos=find(val);
return T[ls(pos)].siz+1;
}

这个简单,如果我们找到了权值为x的节点,那么答案就是他的左子树的大小+1

否则的话根据二叉查找树的性质不断的向下走就可以,注意如果这次是向右走的话答案需要加上它左子树的大小和这个节点的rec值

int rank(int v)// 查询值为v的数的排名
{
int ans=0,now=root;
while(1)
{
if(tree[now].v==v) return ans+tree[tree[now].ch[0]].sum+1;
if(now==0) return 0;
if(v<tree[now].v) now=tree[now].ch[0];
else ans+=tree[tree[now].ch[0]].sum+tree[now].rec,now=tree[now].ch[1];
}
if(now) splay(now,root);
return 0;
}

查询排名为x的数

这个操作就是上面那个操作的逆向操作

用used变量记录该节点以及它的左子树有多少节点

如果x>左子树的数量且< used,那么当前节点的权值就是答案

否则根据二叉查找树的性质继续向下走

同样注意在向右走的时候要更新x

int arank(int x)//查询排名为x的数是什么
{
int now=root;
while(1)
{
int used=tree[now].sum-tree[tree[now].ch[1]].sum;
if(x>tree[tree[now].ch[0]].sum&&x<=used) break;
if(x<used) now=tree[now].ch[0];
else x=x-used,now=tree[now].ch[1];
}
splay(now,root);
return tree[now].v;
}

求x的前驱

这个更容易,我们可以维护一个ans变量,然后对整棵树进行遍历,同时更新ans

int lower(int v)// 小于v的最大值
{
int now=root;
int ans=-maxn;
while(now)
{
if(tree[now].v<v&&tree[now].v>ans) ans=tree[now].v;
if(v>tree[now].v) now=tree[now].ch[1];
else now=tree[now].ch[0];
}
return ans;
}

求x的后继

这个和上一个一样,就不细讲了

int upper(int v)
{
int now=root;
int ans=maxn;
while(now)
{
if(tree[now].v>v&&tree[now].v<ans) ans=tree[now].v;
if(v<tree[now].v) now=tree[now].ch[0];
else now=tree[now].ch[1];
}
return ans;
}

完整代码:

// luogu-judger-enable-o2
// luogu-judger-enable-o2
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define ls(x) T[x].ch[0]
#define rs(x) T[x].ch[1]
#define fa(x) T[x].fa
#define root T[0].ch[1]
using namespace std;
const int MAXN=1e5+10,mod=10007,INF=1e9+10;
inline char nc()
{
static char buf[MAXN],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,MAXN,stdin)),p1==p2?EOF:*p1++;
}
inline int read()
{
char c=nc();int x=0,f=1;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=nc();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=nc();}
return x*f;
}
struct node
{
int fa,ch[2],val,rec,sum;
}T[MAXN];
int tot=0,pointnum=0;
void update(int x){T[x].sum=T[ls(x)].sum+T[rs(x)].sum+T[x].rec;}
int ident(int x){return T[fa(x)].ch[0]==x?0:1;}
void connect(int x,int fa,int how){T[fa].ch[how]=x;T[x].fa=fa;}
void rotate(int x)
{
int Y=fa(x),R=fa(Y);
int Yson=ident(x),Rson=ident(Y);
connect(T[x].ch[Yson^1],Y,Yson);
connect(Y,x,Yson^1);
connect(x,R,Rson);
update(Y);update(x);
}
void splay(int x,int to)
{
to=fa(to);
while(fa(x)!=to)
{
int y=fa(x);
if(T[y].fa==to) rotate(x);
else if(ident(x)==ident(y)) rotate(y),rotate(x);
else rotate(x),rotate(x);
}
}
int newnode(int v,int f)
{
T[++tot].fa=f;
T[tot].rec=T[tot].sum=1;
T[tot].val=v;
return tot;
}
void Insert(int x)
{
int now=root;
if(root==0) {newnode(x,0);root=tot;}//
else
{
while(1)
{
T[now].sum++;
if(T[now].val==x) {T[now].rec++;splay(now,root);return ;}
int nxt=x<T[now].val?0:1;
if(!T[now].ch[nxt])
{
int p=newnode(x,now);
T[now].ch[nxt]=p;
splay(p,root);return ;
}
now=T[now].ch[nxt];
}
}
}
int find(int x)
{
int now=root;
while(1)
{
if(!now) return 0;
if(T[now].val==x) {splay(now,root);return now;}
int nxt=x<T[now].val?0:1;
now=T[now].ch[nxt];
}
}
void delet(int x)
{
int pos=find(x);
if(!pos) return ;
if(T[pos].rec>1) {T[pos].rec--,T[pos].sum--;return ;}
else
{
if(!T[pos].ch[0]&&!T[pos].ch[1]) {root=0;return ;}
else if(!T[pos].ch[0]) {root=T[pos].ch[1];T[root].fa=0;return ;}
else
{
int left=T[pos].ch[0];
while(T[left].ch[1]) left=T[left].ch[1];
splay(left,T[pos].ch[0]);
connect(T[pos].ch[1],left,1);
connect(left,0,1);//
update(left);
}
}
}
int rak(int x)
{
int now=root,ans=0;
while(1)
{
if(T[now].val==x) return ans+T[T[now].ch[0]].sum+1;
int nxt=x<T[now].val?0:1;
if(nxt==1) ans=ans+T[T[now].ch[0]].sum+T[now].rec;
now=T[now].ch[nxt];
}
}
int kth(int x)//排名为x的数
{
int now=root;
while(1)
{
int used=T[now].sum-T[T[now].ch[1]].sum;
if(T[T[now].ch[0]].sum<x&&x<=used) {splay(now,root);return T[now].val;}
if(x<used) now=T[now].ch[0];
else now=T[now].ch[1],x-=used;
}
}
int lower(int x)
{
int now=root,ans=-INF;
while(now)
{
if(T[now].val<x) ans=max(ans,T[now].val);
int nxt=x<=T[now].val?0:1;//这里需要特别注意
now=T[now].ch[nxt];
}
return ans;
}
int upper(int x)
{
int now=root,ans=INF;
while(now)
{
if(T[now].val>x) ans=min(ans,T[now].val);
int nxt=x<T[now].val?0:1;
now=T[now].ch[nxt];
}
return ans;
}
int main()
{
#ifdef WIN32
freopen("a.in","r",stdin);
#else
#endif
int N=read();
while(N--)
{
int opt=read(),x=read();
if(opt==1) Insert(x);
else if(opt==2) delet(x);
else if(opt==3) printf("%d\n",rak(x));
else if(opt==4) printf("%d\n",kth(x));
else if(opt==5) printf("%d\n",lower(x));
else if(opt==6) printf("%d\n",upper(x));
}
return 0;
}

至此,splay最常用的几种函数就解决了,

下面来看几道裸题

例题

不知道为什么,我的splay跑的特别快,可能是脸太好了吧

splay详解(二)的更多相关文章

  1. .NET DLL 保护措施详解(二)关于性能的测试

    先说结果: 加了缓存的结果与C#原生代码差异不大了 我对三种方式进行了测试: 第一种,每次调用均动态编译 第二种,缓存编译好的对象 第三种,直接调用原生C#代码 .net dll保护系列 ------ ...

  2. PopUpWindow使用详解(二)——进阶及答疑

      相关文章:1.<PopUpWindow使用详解(一)——基本使用>2.<PopUpWindow使用详解(二)——进阶及答疑> 上篇为大家基本讲述了有关PopupWindow ...

  3. Android 布局学习之——Layout(布局)详解二(常见布局和布局参数)

    [Android布局学习系列]   1.Android 布局学习之——Layout(布局)详解一   2.Android 布局学习之——Layout(布局)详解二(常见布局和布局参数)   3.And ...

  4. logback -- 配置详解 -- 二 -- <appender>

    附: logback.xml实例 logback -- 配置详解 -- 一 -- <configuration>及子节点 logback -- 配置详解 -- 二 -- <appen ...

  5. 爬虫入门之urllib库详解(二)

    爬虫入门之urllib库详解(二) 1 urllib模块 urllib模块是一个运用于URL的包 urllib.request用于访问和读取URLS urllib.error包括了所有urllib.r ...

  6. [转]文件IO详解(二)---文件描述符(fd)和inode号的关系

    原文:https://www.cnblogs.com/frank-yxs/p/5925563.html 文件IO详解(二)---文件描述符(fd)和inode号的关系 ---------------- ...

  7. Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)

    View 的绘制系列文章: Android View 的绘制流程之 Measure 过程详解 (一) Android View 绘制流程之 DecorView 与 ViewRootImpl 在上一篇  ...

  8. HTTPS详解二:SSL / TLS 工作原理和详细握手过程

    HTTPS 详解一:附带最精美详尽的 HTTPS 原理图 HTTPS详解二:SSL / TLS 工作原理和详细握手过程 在上篇文章HTTPS详解一中,我已经为大家介绍了 HTTPS 的详细原理和通信流 ...

  9. Linux dts 设备树详解(二) 动手编写设备树dts

    Linux dts 设备树详解(一) 基础知识 Linux dts 设备树详解(二) 动手编写设备树dts 文章目录 前言 硬件结构 设备树dts文件 前言 在简单了解概念之后,我们可以开始尝试写一个 ...

  10. pika详解(二) BlockingConnection

    pika详解(二) BlockingConnection   本文链接:https://blog.csdn.net/comprel/article/details/94592348 版权 Blocki ...

随机推荐

  1. ARVE: Augmented Reality Applications in Vehicle to Edge Networks

    ARVE:车辆到边缘网中的增强现实应用 本文为SIGCOMM 2018 Workshop (Mobile Edge Communications, MECOMM)论文. 笔者翻译了该论文.由于时间仓促 ...

  2. 【同余方程组】POJ1006 生理周期

    同余方程组: 先来看一道题目:有物不知其数,三三数之剩二:五五数之剩三:七七数之剩二.问物几何?  然后我们可以做如下变换,设x为所求的数. x%3=2              x ≡ a1(%m1 ...

  3. Redis 设计与实现 (七)--事务

    事务 *ACID,指数据库事务正确执行的四个基本要素的缩写.包含:原子性(Atomicity).一致性(Consistency).隔离性(Isolation).持久性(Durability) redi ...

  4. iOS崩溃日志ips文件解析

    iOS崩溃日志ips文件解析  一 简介 测试组的同事在进行稳定性测试时,通常会遇到一些崩溃,然后他们会将这些崩溃日志(一般是ips格式的文件)反馈给开发进行分析,但是这些ips文件中的内容通常是如下 ...

  5. Android-线程池下载多个图片并保存,如果本地有该图,则不下载,直接展示到view

    做了个工具方法,用来下载图片,如果本地有这个图,则不下载,直接展示到view setHP()方法可以多次使用,因为使用了线程池,所以是个异步操作,如果使用的多,建议根据需要增加线程池的线程数量 看代码 ...

  6. 关于CGI 和 PHP-FPM需要弄清的

    https://blog.csdn.net/gao_yu_long/article/details/79390510 补充一点: 如果php-cgi -b 127.0.0.1:6999,则6999端口 ...

  7. MySQL优化配置之query_cache_size

    原理MySQL查询缓存保存查询返回的完整结果.当查询命中该缓存,会立刻返回结果,跳过了解析,优化和执行阶段.  查询缓存会跟踪查询中涉及的每个表,如果这写表发生变化,那么和这个表相关的所有缓存都将失效 ...

  8. HBase篇--HBase操作Api和Java操作Hbase相关Api

    一.前述. Hbase shell启动命令窗口,然后再Hbase shell中对应的api命令如下. 二.说明 Hbase shell中删除键是空格+Ctrl键. 三.代码 1.封装所有的API pa ...

  9. log4cplus使用(三)-日志重定向

    本文讲述的是log4cplus日志输出到qt widget,封装了serverSocket.     log4cplus支持用户自定义输出设备,只需要继承自Appender,或者Appender子类, ...

  10. 『扩展欧几里得算法 Extended Euclid』

    Euclid算法(gcd) 在学习扩展欧几里得算法之前,当然要复习一下欧几里得算法啦. 众所周知,欧几里得算法又称gcd算法,辗转相除法,可以在\(O(log_2b)\)时间内求解\((a,b)\)( ...