首先来说是splay是二叉搜索树,它可以说是线段树和SBT的综合,更可以解决一些二者解决不了的问题,splay几乎所有的操作都是由splay这一操作完成的,在介绍这一操作前我们先介绍几个概念和定义

  二叉搜索树,即BST(binary search tree),这样的树有一个关键字,满足对于每个节点来说,以该节点左儿子为根节点的子树中的所有节点的关键字小于该节点的关键字,以该节点右儿子为根节点的子树中的所有节点的关键字大于该节点的关键字。

  splay主要可以用来解决区间的维护问题

  假设我们需要维护一个数列,支持

  1.在数列第i位后插入一个长为l的数列

  2.在数列第i为后删除一个长为l的数列 

  3.将数列的l r区间翻转(1 2 3  2 3 翻转后为 3 2 3 2 1)

  4.将数列的l r区间同时加上一个值

  5.将数列的l r区间同时改为一个值

  6.求数列的l r区间的和(最大值)

  其实线段树上的大部分操作这里都支持,比如区间最大子区间和

  首先对于当前的树,它的中序遍历就是当前的区间,每个点的关键字(二叉搜索树的那个)是内个点表示区间元素的标号,比如一个点的关键字是3,那么这个点代表区间中第3个元素,每个点除了关键字外还记录了一个tree[i]代表这个点对应区间内的元素是什么。

  上图(节点内的数代表tree值)的树表示数列 3 7 1 4 2 -1

  对于每个节点的记录内容为

  son[x,0..1]左右儿子

  father[x]父亲节点

  还有我们定义root为当前树的根节点,sroot为超级节点(-1),sroot只连接着root(其实就是定义了root的father为-1)

  那么我们首先建树的时候,具体过程为

function build(l,r:longint):longint;
var
mid :longint;
begin
mid:=(l+r) div ;
tree[mid]:=a[mid];//a为区间的值
if l<=mid- then
begin
son[mid,]:=build(l,mid-);
father[son[mid,]]:=mid;
end;
if mid+<=r then
begin
son[mid,]:=build(mid+,r);
father[son[mid,]]:=mid;
end;
update(mid);//可暂时忽略
exit(mid);
end;

  那么我们现在有了一颗树,我们还需要改变这棵树的形态,就是splay(x,y)代表将编号为x的点旋转到y的儿子处,那么我们就需要介绍一个旋转操作了,在介绍旋转操作之前还应该引入一个find操作,假设我们需要找区间内第i个元素,树中代表这个点的编号是多少(每个点都有一个编号,编号随意定,满足互不相同就行了,类似于线段树,SBT中的点的编号,没有实际意义)我们规定一个点的size值为以该点为根节点的子树的节点数,那么find(l)表示数列中第l个元素在树中的编号。

function find(x:longint):longint;
var
t :longint;
begin
t:=root;
while true do
begin
push_down(t);//可暂时忽略
if size[son[t,]]+=x then exit(t);
if size[son[t,]]+>x then t:=son[t,]
else
begin
dec(x,size[son[t,]]+);
t:=son[t,];
end;
end;
end;

  那么我们介绍旋转过程rotate(x,y)代表将编号为x的节点旋转到他的父亲节点,就是如果x是左儿子就右旋father[x],右儿子就左旋father[x],y代表x是他父亲的左节点(0)还是右节点(1)。

procedure rotate(x,y:longint);
var
f :longint;
begin
push_down(x);
f:=father[x];
father[son[x,y xor ]]:=f;
son[f,y]:=son[x,y xor ];
if f=root then root:=x
else
if f=son[father[f],] then
son[father[f],]:=x else
son[father[f],]:=x;
father[x]:=father[f];
father[f]:=x;
son[x,y xor ]:=f;
update(f);
update(x);
end;

  那么对于splay过程我们就可以理解了

procedure splay(x,y:longint);
var
u, v :longint;
begin
while father[x]<>y do
if father[father[x]]=y then
rotate(x,ord(x=son[father[x],])) else
begin
if son[father[x],]=x then u:= else u:=-;
if son[father[father[x]],]=father[x] then v:= else v:=-;
if u*v= then
begin
rotate(father[x],ord(x=son[father[x],]));
rotate(x,ord(x=son[father[x],]));
end else
begin
rotate(x,ord(x=son[father[x],]));
rotate(x,ord(x=son[father[x],]));
end;
end;
update(x);
end;

  其中u=1代表x是父亲的左节点,u=-1代表是右节点,v=1代表x父亲是x爷爷的左节点,v=-1代表右节点

  那么v*u=1的情况就是x和父亲,爷爷,祖孙三代是一条链(直观的说)这种情况先旋父亲,再旋x,否则旋两次x,其实结果是一样的,但是前人证明这样操作会使splay树更平衡些。

  那么剩下的操作就是基于这几个操作的扩展了,比如添加区间,在l后加入长s的区间

for i:=n+ to n+s do read(a[i]);
p:=build(n+,n+s);//把这一区间建成一棵树我们只需要插入p节点就行了
q:=find(l); splay(q,sroot);
q:=find(l+); splay(q,root);
son[son[root,],]:=p;
father[p]:=son[root,];
update(son[root,]);
update(root);

  其中两个find和splay操作是精华,我们先找到第l个元素,旋转到根,再找到第l+1个元素,旋转到根的右儿子,那么第l+1个节点是没有左儿子的(因为当前以l为根,l+1元素左儿子代表比l大的,比l+1小的,显然没有),那么我们不是要在L后面插入区间么,就直接将p点当成l+1点的左儿子就行了。

  那么我们会发现,假如我要在区间的开头插入区间怎么办find(0)是没有值的,那么我们就插入左右标兵,在最开始建树的时候inc(n),root:=build(0,n);

  其实这样多插入了两个数,那么我们要find(l)时需要find(l+1),以后每次用find的时候+1就好了

  那么对于删除操作假设删除l r区间

p:=find(l); splay(p,sroot);
p:=find(r+); splay(p,root);
son[son[root,],]:=-;
update(son[root,]);
update(root);

  我们将区间中第l-1个元素旋转到根节点,r+1个元素旋转到根节点的右儿子,那么以son[son[root,1],0]为根节点的子树代表的就是区间l r,直接删除就好,那么对于区间最大值操作,类似于线段树就行了,因为旋转后树的结构已经改变了,那么我们需要维护节点存储的信息,就是update操作

procedure update(x:longint);
begin
sum[x]:=sum[son[x,]]+tree[x]+sum[son[x,]];
size[x]:=size[son[x,]]++size[son[x,]];
max[x]:=get_max(tree[x],get_max(max[son[x,]],max[son[x,]]));
end;

  对于区间赋值,修改这样的,打标签就好了,那么对于区间翻转操作我们也可以打标签,flag[x]为true代表以x为根节点的区间需要翻转,那么我们旋转一个区间的时候,假设根节点为x。

proceudre reverse(x:longint);
begin
swap(son[x,],son[x,]);
flag[son[x,]]:=not flag[son[x,]];
flag[son[x,]]:=not flag[son[x,]];
end;

  可以自己举个例子,发现满足这个性质

  push_down操作则为下放标签

procedure push_down(x:longint);
var
l,r :longint;
begin
l:=son[x,0];r:=son[x,1];
if flag[x] then
begin
if l<>-1 then renew(l,0);
if r<>-1 then renew(r,0);
flag[x]:=false;
end;
if val[x]<>0 then
begin
if l<>-1 then renew(l,val[x]);
if r<>-1 then renew(r,val[x]);
val[x]:=0;
end;
end;

平衡树之splay讲解的更多相关文章

  1. Splay讲解

    Splay讲解 Splay是平衡树的一种,是一种二叉搜索树,我们先讲解一下它的核心部分. Splay的核心部分就是splay,可能有些人会说什么鬼?这样讲解是不是太不认真了?两个字回答:不是.第一个S ...

  2. P3391 【模板】文艺平衡树(Splay)新板子

    P3391 [模板]文艺平衡树(Splay) 题目背景 这是一道经典的Splay模板题——文艺平衡树. 题目描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转 ...

  3. fhq_treap || BZOJ 3223: Tyvj 1729 文艺平衡树 || Luogu P3391 【模板】文艺平衡树(Splay)

    题面: [模板]文艺平衡树(Splay) 题解:无 代码: #include<cstdio> #include<cstring> #include<iostream> ...

  4. 【转】 史上最详尽的平衡树(splay)讲解与模板(非指针版spaly)

    ORZ原创Clove学姐: 变量声明:f[i]表示i的父结点,ch[i][0]表示i的左儿子,ch[i][1]表示i的右儿子,key[i]表示i的关键字(即结点i代表的那个数字),cnt[i]表示i结 ...

  5. splay模板三合一 luogu2042 [NOI2005]维护数列/bzoj1500 [NOI2005]维修数列 | poj3580 SuperMemo | luogu3391 【模板】文艺平衡树(Splay)

    先是维修数列 题解看这里,但是我写的跑得很慢 #include <iostream> #include <cstdio> using namespace std; int n, ...

  6. [知识点]平衡树之Splay

    // 此博文为迁移而来,写于2015年7月18日,不代表本人现在的观点与看法.原始地址:http://blog.sina.com.cn/s/blog_6022c4720102w6rg.html 1.前 ...

  7. 【BZOJ】3223: Tyvj 1729 文艺平衡树(splay)

    http://www.lydsy.com/JudgeOnline/problem.php?id=3223 默默的.. #include <cstdio> #include <cstr ...

  8. 平衡树(Splay):Splaytree POJ 3580 SuperMemo

    SuperMemo         Description Your friend, Jackson is invited to a TV show called SuperMemo in which ...

  9. bzoj3223 Tyvj 1729 文艺平衡树(Splay Tree+区间翻转)

    3223: Tyvj 1729 文艺平衡树 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 2202  Solved: 1226[Submit][Sta ...

随机推荐

  1. error LNK2001: unresolved external symbol "public: __thiscall ControllerInterface::ControllerInterface(class QObject *)" (??0ControllerInterface@@QAE@PAVQObject@@@Z) downloadcontroller.obj

    前几天刚遇到这个问题,但是今天再碰到就又要思考怎么解决.这次特别记录一下,以防下次碰到再手足无措: 1.看到这个报错第一感觉LNK关键字,表示连接错误,这种错误有几个可以下手的点 1)函数声明和定义是 ...

  2. 【APUE】Chapter5 Standard I/O Library

    5.1 Introduction 这章介绍的standard I/O都是ISOC标准的.用这些standard I/O可以不用考虑一些buffer allocation.I/O optimal-siz ...

  3. C++学习002-C++代码中插入汇编语句

    在C++中我们有时会遇到使用汇编语言的情况,这时可以在前面加上关键字“_asm”宏. 如下示例 编写环境 :vs2015 int main() { __asm mov al, 0x20; __asm ...

  4. 怎么在windows10中关闭Windows Defender?

    通过修改注册表,永久禁用Windows Defender 打开注册表编辑器. 按 Win +R键入regedit,点击确定.    定位需要修改的注册表 其路径如下 HKEY_LOCAL_MACHIN ...

  5. SPOJ 1812 Longest Common Substring II(后缀自动机)(LCS2)

    A string is finite sequence of characters over a non-empty finite set Σ. In this problem, Σ is the s ...

  6. C++的几种字符类型

    我们在C学过了char字符类型. 在C++中,char是基本的字符类型,但却不仅仅有这一种字符类型! 类型 含义 该类型数据所占的最小比特位数 char 字符 8位(即可表示28个字符) wchar_ ...

  7. [热键冲突]MacOS下 Pycharm的全局搜索Ctrl+Shift+F失灵

    刚换了MacOS 发现Pycharm下的全局搜索Ctrl+Shift+F失灵了, 经过帖子 https://blog.csdn.net/pxinm/article/details/64444560 知 ...

  8. Problem Collection I 位运算

    XOR ARC 092B CF 959E xor-MST CF 959F

  9. Android逆向之旅---静态方式分析破解视频编辑应用「Vue」水印问题

    一.故事背景 现在很多人都喜欢玩文艺,特别是我身边的UI们,拍照一分钟修图半小时.就是为了能够在朋友圈显得逼格高,不过的确是挺好看的,修图的软件太多了就不多说了,而且一般都没有水印啥的.相比较短视频有 ...

  10. 【BZOJ 1407】[Noi2002]Savage ExGCD

    我bitset+二分未遂后就来用ExGCD了,然而这道题的时间复杂度还真是玄学...... 我们枚举m然后对每一对用ExGCD判解,我们只要满足在最小的一方死亡之前无解就可以了,对于怎么用,就是ax+ ...