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

(这里是Splay基础操作,reserve什么的会在下一篇里面讲)

好久之前就说要学Splay了,结果苟到现在才学习。

可能是最近良心发现自己实在太弱了,听数学又听不懂只好多学点不要脑子的数据结构。

感觉Splay比Treap良心多了——代码真的好写。

对于Splay显然可以维护Treap的所有操作,并且本质是BST。

先看看Splay是怎么维护普通平衡树操作的吧。

首先先定义一些基础的变量(若不作特殊说明这些变量的意义不变)

int t[N][2] // t[x][0]表示节点x的左子树,t[x][1]表示节点x的右子树

int cnt[N] // cnt[x]表示节点x存储多少个重复的数

int val[N] // val[x]表示节点x存储数的大小

int par[N] // par[x]表示节点x的直接父亲,特别的,根节点的直接父亲为0

int size[N] // size[x]表示在BST中x子树中存储数的个数

# define ls(x) (t[x][0])

# define rs(x) (t[x][1]) //方便书写

初始化:Splay平衡树中有有+无穷 -无穷两个哨兵节点!!!

Check(x) 函数 :

询问节点x是其父亲的左儿子(return 0)还是右孩子(return 1)

int check (int x)  { return rs(par[x])==x;
}

由上述代码可知,根节点的check(root)值为0,即根节点是0节点的左儿子(Nothing Special)

Up(x)函数:

对于节点x维护其size值为两个孩子的size值+自身cnt值。

void up(int x){size[x]=size[ls(x)]+size[rs(x)]+cnt[x];
}

Rotate(x)函数:

对于节点x旋转到其父亲节点,且不改变树BST性质。(使得树形态较为随机)

这里需要解释一下Rotate的解释和记忆方法(和Treap中Rotate类似)

对于一棵有根树(且父亲指向儿子的边有向),我们现在以把左儿子旋到父节点为例。

第1步,考虑4的右子树已经有元素了,考虑把右子树连接到父节点左儿子处。不改变BST性质。

在此基础上考虑第二步,就是吧2(4的直接父亲接到4的右边)不改变BST性质。

这个时候我们会发现,只进行第三部就可以完成一次rotate操作。

即连一条1指向4的边即可。

对于左边节点转到父亲节点,一般称之为右旋

对于右旋函数的代码,不难得到。

void rotate(int x){int y=par[x];t[y][0]=t[x][1]; par[t[x][1]]=y;t[x][1]=y; t[par[y]][check(y)]=x;par[x]=par[y]; par[y]=x;up(y); up(x);
}
右旋

那左旋呢???

所有0和1的地方去个反不就好了!!!(至少我是那么记的)

对于 真正的旋转代码:

void rotate(int x){int y=par[x],k=check(x);t[y][k]=t[x][k^1]; par[t[x][k^1]]=y;t[x][k^1]=y; t[par[y]][check(y)]=x;par[x]=par[y]; par[y]=x;up(y); up(x);
}

Splay(x,goal) 操作

把节点x通过若干次rotate操作使其到达目标节点goal或者为根,而goal却成为节点x的儿子。

我们可以分三种情况讨论:

1. goal 是 x的直接父亲(边界),那么直接将x旋到父亲位置即可

2. x和x的直接父亲y和x的爷爷z在同一条直线上(不会打结吗?我们需要给定一种顺序)

那么先旋转父亲y,再旋转x,这个时候想就被旋转到y和z节点上方了。

3. x和x的直接父亲y和x的爷爷z在不在同一条直线上(直接把x转两次不就行了么)

我们可以参考下图,模拟一条链上的Splay操作。

 简单的代码实现如下(自然语言是多么的无力....)

void splay(int x,int goal=0) {while (par[x]!=goal) {int y=par[x],z=par[y];if (z!=goal) {if (check(x)==check(y)) rotate(y);else rotate(x);}rotate(x);}if (!goal) root=x;
}

 Insert(x)操作

插入一个值为x的元素。

显然从根节点开始按照BST性质访问Splay,直到找到一个节点v,其值val[v]恰好为x,那么直接增加个数

如果找不到节点的的val[v]恰好为x,那么新建一个节点即可。

最后Splay一下防止出现长链的情况。

void insert(int x) {int cur=root,p=0;while (cur && val[cur]!=x)p=cur,cur=t[cur][x>val[cur]];if (cur) cnt[cur]++;else {cur=++tot;if (p) t[p][x>val[p]]=cur;ls(cur)=rs(cur)=0;par[cur]=p; val[cur]=x;size[cur]=cnt[cur]=1;}splay(cur);
}

Find(x)函数

将val值小于等于x的val值最大一个节点,旋转到根。

题目解决在于如何找到val值小于等于x的val最大的一个节点,注意不能找到空节点,所以要判断。

找到节点v直接利用Splay操作,旋转到根即可。

void find(int x){if (!root) return;int cur=root;while (t[cur][x>val[cur]] && val[cur]!=x)cur=t[cur][x>val[cur]];splay(cur);
}

Rank(x)函数

求值x的排名(x可能曾经没有出现过),排名的定义是比x小的元素个数+1。

利用find操作后,如果x之前出现过,即val[root]=x,那么直接输出左子树的size

否则那么根节点一定比x小那么还需要加上根节点的cnt

int rank(int x){find(x);if (val[root]>=x) return size[ls(root)];else return size[ls(root)]+cnt[root]-1;
}

pre(x)和suc(x)函数

求值x的前驱(比x小的最大数,若没有是-无穷),求值x的后继(比x大的最小数,若没有是+无穷),x可能没有出现过。

求前驱,考虑find操作以后小于等于x的元素都在根及根的左侧,那么如果根直接小于x(x之前没出现过),那么直接打印根就行

否则在左子树中找一直往右子树走找最大的即可。

求后继,考虑find操作以后大于等于x的元素都在根及根的左侧,那么如果根直接大于x(x之前没出现过),那么直接打印根就行

否则在右子树中找一直往左子树走找最小的即可。

int pre_id(int x) {find(x);if (val[root]<x) return root;int cur=ls(root);while (rs(cur)) cur=rs(cur);return cur;
}    
int pre_val(int x){return val[pre_id(x)];
}
int suc_id(int x) {find(x);if (val[root]>x) return root;int cur=rs(root);while (ls(cur)) cur=ls(cur);return cur;
}
int suc_val(int x){return val[suc_id(x)];
}

erase(x)操作

删除权值为x的一个数。

考虑数x的前驱和后继是唯一的,那么求出x的前驱和x的后继,均用Splay操作转到根节点和根节点的右儿子处,

那么根节点右儿子的左儿子一定就可知道是x的了。直接删除它即可,特别的是,剩余个数大于1和等于1的时候需要不同处理

其中大于1的时候,直接吧cnt减去1即可,等于1的时候,则需要删除节点所有的信息。

void erase(int x){int last=pre_id(x),next=suc_id(x);splay(last),splay(next,last);int d=ls(rs(root));if (cnt[d]>1) cnt[d]--,splay(d);else t[next][0]=0;
}

Treap模板题目:https://www.luogu.org/problemnew/show/P3369

# include <bits/stdc++.h>
using namespace std;
const int N=2e5+10;
struct Splay{# define ls(x) (t[x][0])# define rs(x) (t[x][1])# define inf (0x3f3f3f3f)int t[N][2],cnt[N],val[N],size[N],par[N];int root,tot;Splay() { tot=root=0; insert(-inf); insert(inf);}int check(int x) { return rs(par[x])==x; }void up(int x){size[x]=size[ls(x)]+size[rs(x)]+cnt[x];}void rotate(int x){int y=par[x],k=check(x);t[y][k]=t[x][k^1]; par[t[x][k^1]]=y;t[x][k^1]=y; t[par[y]][check(y)]=x;par[x]=par[y]; par[y]=x;up(y); up(x);}void splay(int x,int goal=0) {while (par[x]!=goal) {int y=par[x],z=par[y];if (z!=goal) {if (check(x)==check(y)) rotate(y);else rotate(x);}rotate(x);}if (!goal) root=x;}void insert(int x) {int cur=root,p=0;while (cur && val[cur]!=x)p=cur,cur=t[cur][x>val[cur]];if (cur) cnt[cur]++;else {cur=++tot;if (p) t[p][x>val[p]]=cur;ls(cur)=rs(cur)=0;par[cur]=p; val[cur]=x;size[cur]=cnt[cur]=1;}splay(cur);}void find(int x){if (!root) return;int cur=root;while (t[cur][x>val[cur]] && val[cur]!=x)cur=t[cur][x>val[cur]];splay(cur);}int pre_id(int x) {find(x);if (val[root]<x) return root;int cur=ls(root);while (rs(cur)) cur=rs(cur);return cur;}int pre_val(int x){return val[pre_id(x)];}int suc_id(int x) {find(x);if (val[root]>x) return root;int cur=rs(root);while (ls(cur)) cur=ls(cur);return cur;}int suc_val(int x){return val[suc_id(x)];}int rank(int x){find(x);if (val[root]>=x) return size[ls(root)];else return size[ls(root)]+cnt[root]-1;}int kth_id(int k) {if (k>tot||k<0) return -1;int cur=root;while (true) {if (t[cur][0]&&k<=size[ls(cur)]) cur=ls(cur);else if (k>size[ls(cur)]+cnt[cur]) {k-=size[ls(cur)]+cnt[cur];cur=rs(cur);} else return cur;}}int kth_val(int k){return val[kth_id(k+1)];}void erase(int x){int last=pre_id(x);int next=suc_id(x);splay(last);splay(next,last);int d=ls(rs(root));if (cnt[d]>1) cnt[d]--,splay(d);else t[next][0]=0;}
}tr;
int main()
{int T; scanf("%d",&T);while (T--) {int op,x;scanf("%d%d",&op,&x);switch(op) {case 1:tr.insert(x);break;case 2:tr.erase(x);break;case 3:printf("%d\n",tr.rank(x));break;case 4:printf("%d\n",tr.kth_val(x));break;case 5:printf("%d\n",tr.pre_val(x));break;case 6:printf("%d\n",tr.suc_val(x));break;}}return 0;
}
P3369-Splay做法

 

转载于:https://www.cnblogs.com/ljc20020730/p/10597659.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/249348.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AnswerOpenCV(1001-1007)一周佳作欣赏

外国不过十一&#xff0c;所以利用十一假期&#xff0c;看看他们都在干什么。一、小白问题http://answers.opencv.org/question/199987/contour-single-blob-with-multiple-object/ Contour Single blob with multiple objectHi to everyone. Im developing an object shape id…

云开发创建云函数

安装wx-server-sdk时候&#xff0c;终端报错如下&#xff1a; 解决方法&#xff1a; 运行&#xff1a;npm cache clean --force即可 转载于:https://www.cnblogs.com/moguzi12345/p/9758842.html

CSS3笔记之基础篇(一)边框

效果一、圆角效果 border-radius 实心上半圆&#xff1a; 方法&#xff1a;把高度(height)设为宽度&#xff08;width&#xff09;的一半&#xff0c;并且只设置左上角和右上角的半径与元素的高度一致&#xff08;大于也是可以的&#xff09;。 div {height:50px;/*是width…

CSS3笔记之基础篇(二)颜色和渐变色彩

效果一、颜色之RGBA RGB是一种色彩标准&#xff0c;是由红(R)、绿(G)、蓝(B)的变化以及相互叠加来得到各式各样的颜色。RGBA是在RGB的基础上增加了控制alpha透明度的参数。 语法&#xff1a; color&#xff1a;rgba(R,G,B,A) 以上R、G、B三个参数&#xff0c;正整数值的取值…

19_03_26校内训练[魔法卡片]

题意 有n张有序的卡片&#xff0c;每张卡片上恰有[1,m]中的每一个数&#xff0c;数字写在正面或反面。每次询问区间[l,r]&#xff0c;你可以将卡片上下颠倒&#xff0c;问区间中数字在卡片上方的并的平方和最大是多少。q,n*m≤1,000,000。 思考 一个很重要的性质&#xff0c;若…

CSS3笔记之基础篇(三)文字与字体

要点一、text-overflow与word-wrap text-overflow&#xff1a;设置是否使用一个省略标记&#xff08;...&#xff09;标示对象内文本的溢出。 word-wrap&#xff1a;设置文本行为&#xff0c;当前行超过指定容器的边界时是否断开转行。 语法如下&#xff1a; 注意&#xff1…

XV6操作系统代码阅读心得(二):进程

1. 进程的基本概念 从抽象的意义来说&#xff0c;进程是指一个正在运行的程序的实例&#xff0c;而线程是一个CPU指令执行流的最小单位。进程是操作系统资源分配的最小单位&#xff0c;线程是操作系统中调度的最小单位。从实现的角度上讲&#xff0c;XV6系统中只实现了进程&…

.Net Core 商城微服务项目系列(十二):使用k8s部署商城服务

一、简介 本篇我们将会把商城的服务部署到k8s中&#xff0c;同时变化的还有以下两个地方&#xff1a; 1.不再使用Consul做服务的注册和发现&#xff0c;转而使用k8s-dns来实现。 2.不再使用Ocelot作为业务网关&#xff0c;使用Traefik来实现。 正如上面所讲&#xff0c;服务发现…

CSS知识体系图谱

转自&#xff1a;https://blog.csdn.net/A13330069275/article/details/78448415

python2 pip安装包等出现各种编码错误UnicodeDecodeError: 'ascii'(/或者utf-8) codec can't decode byte 0xd2......

1.问题描述&#xff1a; python2环境&#xff0c;pip安装包时报错UnicodeDecodeError: ascii(/或者utf-8) codec cant decode byte 0xd2... 类似如下情况 2.原因分析 一开始依据网上给出的教程修改python安装路径下的各种文件&#xff0c;添加各种编码&#xff0c;始终无法解决…

leetcood学习笔记-111-二叉树的最小深度

题目描述&#xff1a; 第一次提交&#xff1a; class Solution(object):def minDepth(self, root):""":type root: TreeNode:rtype: int"""if not root:return 0if root.left and root.right:return min(self.minDepth(root.left)1,self.minDept…

IPV6 简单总结

1. 转帖别人的内容 来源&#xff1a;https://www.2cto.com/net/201112/114937.html 2. 本地用IPV6单播地址 (包括链路本地单播地址 和 站点本地单播地址) 2.1 链路本地单播地址 规定了链路本地和站点本地两种类型的本地使用单播地址。链路本地地址用在单链路上&#xff0c; 而…

面向对象第一单元总结

一、对面向对象的理解 有位同学给java的面向对象做了一个形象生动的类比&#xff0c;我觉得很有道理&#xff0c;大概按我的理解如下&#xff1a; 结构的形成看图之前&#xff0c;我们要先明白&#xff0c;世界上是先有了实体&#xff0c;才有了一步步抽象至以上的体系结构&…

理解HTML语义化

1、什么是HTML语义化&#xff1f; <基本上都是围绕着几个主要的标签&#xff0c;像标题&#xff08;H1~H6&#xff09;、列表&#xff08;li&#xff09;、强调&#xff08;strong em&#xff09;等等> 根据内容的结构化&#xff08;内容语义化&#xff09;&#xff0c;…

基本动态规划题集

观察下面的数字金字塔。写一个程序查找从最高点到底部任意处结束的路径&#xff0c;使路径经过数字的和最大。每一步可以从当前点走到左下方的点也可以到达右下方的点。 在上面的样例中,从13到8到26到15到24的路径产生了最大的和86。 【输入】 第一个行包含R(1≤ R≤1000)&…

python入门学习的第三天

step 1 时间 Python有两个模块&#xff0c;time和calendar&#xff0c;它们可以用于处理时间和日期 首先 import time 导入时间模块 然后 print time.time() 这个叫时间戳&#xff0c;它是从1970年1月1日午夜到现在时刻的秒数 print time.localtime(time.time()) print time.st…

.Net Core应用框架Util介绍(五)

上篇简要介绍了Util在Angular Ts方面的封装情况&#xff0c;本文介绍Angular封装的另一个部分&#xff0c;即Html的封装。 标准组件与业务组件 对于管理后台这样的表单系统&#xff0c;你通常会使用Angular Material或Ng-Zorro这样的UI组件库&#xff0c;它们提供了标准化的U…

Thunar 右键菜单等自定义

Thunar 右键菜单等自定义 可以使用图形界面或者直接编辑配置文件&#xff0c;二者是等价的。 图形界面&#xff1a; 以给“zip&#xff0c;rar&#xff0c;7z”等文件添加“在此位置使用unar解压缩”的右键菜单为例&#xff1a;&#xff08;unar可以很好地处理编码问题&#xf…

学习网站大汇集

一.综合类学习网站&#xff08;中文&#xff09; 1.网易公开课&#xff1a;https://open.163.com/。上面有TED、可汗学院、国内外高校公开课的免费资源。站内内容完全免费&#xff0c;良心推荐。 2.网易云课堂&#xff1a;http://study.163.com/。网易旗下付费学习平台&#…

@Scheduled

Scheduled注解的使用这里不详细说明&#xff0c;直接对8个参数进行讲解。 参数详解 cron 该参数接收一个cron表达式&#xff0c;cron表达式是一个字符串&#xff0c;字符串以5或6个空格隔开&#xff0c;分开共6或7个域&#xff0c;每一个域代表一个含义。 cron表达式语法 […