前言

上一节中,我们讲述了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. libguestfs手册(1): 架构

    要编辑一个image,则运行下面的命令 guestfish -a ubuntutest.img ><fs> 会弹出一个命令行工具 运行run ><fs> run 我 ...

  2. 【安富莱】【RL-TCPnet网络教程】第11章 RL-TCPnet调试方法

    第11章      RL-TCPnet调试方法 本章节为大家讲解RL-TCPnet的调试方法,RL-TCPnet的调试功能其实就是通过串口打印实时监控运行状态.而且RL-TCPnet的调试设置比较简单 ...

  3. #Java学习之路——基础阶段(第五篇)

    我的学习阶段是跟着CZBK黑马的双源课程,学习目标以及博客是为了审查自己的学习情况,毕竟看一遍,敲一遍,和自己归纳总结一遍有着很大的区别,在此期间我会参杂Java疯狂讲义(第四版)里面的内容. 前言: ...

  4. [Swift]LeetCode47. 全排列 II | Permutations II

    Given a collection of numbers that might contain duplicates, return all possible unique permutations ...

  5. [Swift]LeetCode153. 寻找旋转排序数组中的最小值 | Find Minimum in Rotated Sorted Array

    Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand. (i.e. ...

  6. [Swift]LeetCode258. 各位相加 | Add Digits

    Given a non-negative integer num, repeatedly add all its digits until the result has only one digit. ...

  7. [Swift]LeetCode497. 非重叠矩形中的随机点 | Random Point in Non-overlapping Rectangles

    Given a list of non-overlapping axis-aligned rectangles rects, write a function pick which randomly ...

  8. [Swift]LeetCode763. 划分字母区间 | Partition Labels

    A string S of lowercase letters is given. We want to partition this string into as many parts as pos ...

  9. [Swift]LeetCode780. 到达终点 | Reaching Points

    A move consists of taking a point (x, y) and transforming it to either (x, x+y) or (x+y, y). Given a ...

  10. MySql综合知识汇总

    本文实验的测试环境:Windows 10+cmd+MySQL5.6.36+InnoDB Mysql驱动:com.mysql.jdbc.Driver MysqlURL:jdbc:mysql://loca ...