SPLAY or SPALY ?

写在前面:
由我们可爱的Daniel Sleator和Robert Tarjan提出的一种数据结构,平衡树的一种,本质是二叉树。
至于到底是splay还是spaly,我认为可能splay更对一些
毕竟splay是有实意的单词,更有可能一点。而且WIKI百科页也是splay
以下是本人学习splay的一点过程,请多指教喽


SPLAY

那么我在这里复习整理一下spaly的代码相关吧

例题:http://www.lydsy.com/JudgeOnline/problem.php?id=3224

参考博客:http://blog.csdn.net/clove_unique/article/details/50636361

  1 #include<cstdio>
  2 #define maxn 500100
  3 using namespace std;
  4 int root,N,tot;
  5 inline int read(){
  6     register int x=0,t=1;
  7     register char ch=getchar();
  8     while((ch<‘0‘||ch>‘9‘)&&ch!=‘-‘)ch=getchar();
  9     if(ch==‘-‘){t=-1;ch=getchar();}
 10     while(ch>=‘0‘&&ch<=‘9‘){x=x*10+ch-48;ch=getchar();}
 11     return x*t;
 12 }
 13 struct node{
 14     int ch[2],ff,cnt,val,sum;
 15 }t[maxn];
 16 void pushup(int u){
 17     t[u].sum=t[t[u].ch[0]].sum+t[t[u].ch[1]].sum+t[u].cnt;
 18 }
 19 void rotate(int x){
 20     register int y=t[x].ff;
 21     register int z=t[y].ff;
 22     register int k=t[y].ch[1]==x;
 23     t[z].ch[t[z].ch[1]==y]=x;t[x].ff=z;
 24     t[y].ch[k]=t[x].ch[k^1];t[t[x].ch[k^1]].ff=y;
 25     t[x].ch[k^1]=y;t[y].ff=x;
 26     pushup(y),pushup(x);
 27 }
 28 void splay(int x,int goal){
 29     while(t[x].ff!=goal){
 30         int y=t[x].ff;
 31         int z=t[y].ff;
 32         if(z!=goal)
 33             (t[y].ch[0]==x)^(t[z].ch[0]==y)?rotate(x):rotate(y);
 34         rotate(x);
 35     }
 36     if(goal==0)
 37         root=x;
 38 }
 39 void insert(int x){
 40     int u=root,ff=0;
 41     while(u&&t[u].val!=x){
 42         ff=u;
 43         u=t[u].ch[x>t[u].val];
 44     }
 45     if(u)
 46         t[u].cnt++;
 47     else{
 48         u=++tot;
 49         if(ff)
 50             t[ff].ch[x>t[ff].val]=u;
 51         t[tot].ch[0]=0;
 52         t[tot].ch[1]=0;
 53         t[tot].ff=ff;t[tot].val=x;
 54         t[tot].cnt=t[tot].sum=1;
 55     }
 56     splay(u,0);
 57 }
 58 void find(int x){
 59     int u=root;
 60     if(!u)return;
 61     while(t[u].ch[x>t[u].val]&&x!=t[u].val)
 62         u=t[u].ch[x>t[u].val];
 63     splay(u,0);
 64 }
 65 int next(int x,int f){
 66     find(x);
 67     int u=root;
 68     if((t[u].val>x&&f)||(t[u].val<x&&!f))return u;
 69     u=t[u].ch[f];
 70     while(t[u].ch[f^1])u=t[u].ch[f^1];
 71     return u;
 72 }
 73 void del(int x){
 74     int la=next(x,0);
 75     int ne=next(x,1);
 76     splay(la,0),splay(ne,la);
 77     int d=t[ne].ch[0];
 78     if(t[d].cnt>1){
 79         t[d].cnt--;
 80         splay(d,0);
 81     }
 82     else
 83         t[ne].ch[0]=0;
 84 }
 85 int K_th(int x){
 86     int u=root;
 87     if(t[u].sum<x)
 88         return 0;
 89     while(1){
 90         int y=t[u].ch[0];
 91         if(x>t[y].sum+t[u].cnt){
 92             x-=t[y].sum+t[u].cnt;
 93             u=t[u].ch[1];
 94         }
 95         else if(t[y].sum>=x)
 96             u=y;
 97         else
 98             return t[u].val;
 99     }
100 }
101 int main(){
102     insert(-2147483647);
103     insert(+2147483647);
104     N=read();
105     while(N--){
106         int opt=read();
107         if(opt==1)insert(read());
108         else if(opt==2)del(read());
109         else if(opt==3){
110             find(read());
111             printf("%d\n",t[t[root].ch[0]].sum);
112         }
113         else if(opt==4)printf("%d\n",K_th(read()+1));
114         else if(opt==5)printf("%d\n",t[next(read(),0)].val);
115         else if(opt==6)printf("%d\n",t[next(read(),1)].val);
116     }
117     return 0;
118 }

上面那份代码其实是洛谷上的。。。本来想自己写的,但是怎么调都过不去5555(;′д`)ゞ

算了吧,既然抄了代码就要抄的明明白白,这里让我们看看splay到底是怎么维护的吧

首先是一些基本操作:

1.rotate

就是这个东西,保证了二叉树储存的元素顺序不变,大小顺序不变,总之转它就对了。

1 void rotate(int x){
2     register int y=t[x].ff;
3     register int z=t[y].ff;
4     register int k=t[y].ch[1]==x;
5     t[z].ch[t[z].ch[1]==y]=x;t[x].ff=z;
6     t[y].ch[k]=t[x].ch[k^1];t[t[x].ch[k^1]].ff=y;
7     t[x].ch[k^1]=y;t[y].ff=x;
8     pushup(y),pushup(x);
9 }

2.splay

splay是依靠平均操作来降低复杂度的,其实有点玄学,splay这个操作就是要把每次查询和修改的中心重新改变成根,

在这个过程中尽可能的让树的大小平衡,即尽可能打断原先树上存在的链,把他们压成树。。。

反正挺神的,记住写双旋时有先后就对了,尽量让树平衡。

 1 void splay(int x,int goal){
 2     while(t[x].ff!=goal){
 3         int y=t[x].ff;
 4         int z=t[y].ff;
 5         if(z!=goal)
 6             (t[y].ch[0]==x)^(t[z].ch[0]==y)?rotate(x):rotate(y);
 7         rotate(x);
 8     }
 9     if(goal==0)
10         root=x;
11 }

em。。。接下来挑一些重点(我蒙过的)讲吧。。。

3.del
删除操作,为了精确定位我们想删掉的那个点,我们选择找到他的前驱,旋到根上,再找他的后继,旋到前驱下面

这样的话这个点只能在后继的左儿子上了

但是如果这个点没有前驱和后继岂不是药丸???

那我们就干脆插入一个+inf和-inf,保证有前驱和后继,但是相对的作为代价我们还要修改其他的几个函数

 1 void del(int x){
 2     int la=next(x,0);
 3     int ne=next(x,1);
 4     splay(la,0),splay(ne,la);
 5     int d=t[ne].ch[0];
 6     if(t[d].cnt>1){
 7         t[d].cnt--;
 8         splay(d,0);
 9     }
10     else
11         t[ne].ch[0]=0;
12 }

4.查询排名

查找这个数在这里的第几名?首先把他找到再旋到根上(这时候如果这个数不在树里,会发生什么呢?表示蒙圈= ̄ω ̄=)

然后理论上是应该所有比他小的数的个数+1,也就是左儿子sum+1

但是因为咱插了个-inf还得-1,所以就直接是左儿子sum

5.查询排名位k的是谁

同理,还是因为极值的问题,查询第k名就要变成查询k+1名

 1 int K_th(int x){
 2     int u=root;
 3     if(t[u].sum<x)
 4         return 0;
 5     while(1){
 6         int y=t[u].ch[0];
 7         if(x>t[y].sum+t[u].cnt){
 8             x-=t[y].sum+t[u].cnt;
 9             u=t[u].ch[1];
10         }
11         else if(t[y].sum>=x)
12             u=y;
13         else
14             return t[u].val;
15     }
16 }



但是以上只是个平衡树板子,splay作为平衡树中独特的一员,有着自己独特的闪光点(出难题的坑点╰(‵□′)╯

让我们对splay自己独到的用法再进一步探究

P3391 【模板】文艺平衡树(Splay)

这道题是splay裸题,利用的就是splay可以维护区间翻转

具体实现就是首先以每个数的位置作为比较大小的权值,再附上一个标记,表示这个节点及其所有儿子都要交换他们的左右儿子,但谁还都没换(包括自己

这样交换了左右儿子后,就相当于改变了比较大小的方式,原先较小的反而在较大的位置上,就实现了区间反转

例如要翻转(l,r),那么把前驱l旋到根上,后继旋到他后面,这段区间自然就卡成一段子树了,打个标记即可~

对应的K_th函数也有些改动,最后遍历一遍就是答案啦

注意,

所有自上到下的函数(K_th,go)都应该每次pushdown,但insert因为是之前做的,与标记无关,就不用了

而splay这种自下而上的函数则要先把一路的节点记下来,再从上往从下传标记,然后再旋,详细看代码吧

代码(我自己YY了半天才弄出来(ノへ ̄、)好弱啊):

 1 #include<cstdio>
 2 #include<algorithm>
 3 using namespace std;
 4 int n,m,root,tot;
 5 struct node{
 6     int f,val,ch[2],sum;
 7     bool rev;
 8 }t[200005];
 9 void pushup(int x){
10     t[x].sum=t[t[x].ch[0]].sum+t[t[x].ch[1]].sum+1;
11 }
12 void pushdown(int x){
13     if(t[x].rev){
14         t[t[x].ch[0]].rev^=1;
15         t[t[x].ch[1]].rev^=1;
16         t[x].rev^=1;swap(t[x].ch[0],t[x].ch[1]);
17     }
18 }
19 void rotate(int x){
20     register int y=t[x].f;
21     register int z=t[y].f;
22     register int k=t[y].ch[1]==x;
23     t[z].ch[t[z].ch[1]==y]=x;t[x].f=z;
24     t[y].ch[k]=t[x].ch[k^1];t[t[x].ch[k^1]].f=y;
25     t[x].ch[k^1]=y;t[y].f=x;
26     pushup(y),pushup(x);
27 }
28 int st[200005],top;
29 void splay(int x,int goal){
30     top=1;st[1]=x;
31     for(int i=x;t[i].f!=goal;i=t[i].f)st[++top]=t[i].f;
32     for(int i=top;i>=1;i--)pushdown(st[i]);
33     while(t[x].f!=goal){
34         int y=t[x].f;
35         int z=t[y].f;
36         if(z!=goal)
37             (t[z].ch[0]==y)^(t[y].ch[0]==x)?rotate(x):rotate(y);
38         rotate(x);
39     }
40     if(goal==0)
41         root=x;
42 }
43 void insert(int x){
44     int u=root,ff=0;
45     while(u&&t[u].val!=x){
46         ff=u;
47         u=t[u].ch[x>t[u].val];
48     }
49     u=++tot;
50     if(ff)
51         t[ff].ch[x>t[ff].val]=u;
52     t[u].f=ff;t[u].val=x;
53     t[u].rev=0;
54     t[u].ch[0]=0;t[u].ch[1]=0;
55     splay(u,0);
56 }
57 int K_th(int x){
58     int u=root;
59     while(1){
60         pushdown(u);
61         int y=t[u].ch[0];
62         if(x>t[y].sum+1){
63             x-=t[y].sum+1;
64             u=t[u].ch[1];
65         }
66         else if(x<=t[y].sum)u=y;
67         else return u;
68     }
69 }
70 void go(int x){
71     if(!x)return;
72     pushdown(x);
73     go(t[x].ch[0]);
74     st[++top]=t[x].val;
75     go(t[x].ch[1]);
76 }
77 int main(){
78     scanf("%d%d",&n,&m);
79     for(int i=1;i<=n;i++)insert(i);
80     insert(-2147483647);
81     insert(+2147483647);
82     for(int i=1,l,r;i<=m;i++){
83         scanf("%d%d",&l,&r);
84         l--;r++;
85         l=K_th(l+1),splay(l,0);
86         r=K_th(r+1),splay(r,l);
87         t[t[r].ch[0]].rev^=1;
88
89     }
90     top=0;
91     go(root);
92     printf("%d ",st[2]);
93     for(int i=3;i<=n+1;i++)
94     printf("%d ",st[i]);
95     return 0;
96 }

原文地址:https://www.cnblogs.com/2017SSY/p/10158932.html

时间: 2024-10-05 01:42:27

SPLAY or SPALY ?的相关文章

splay复习小记

简介 splay的原名是伸展树,一种超级实用的数据结构,能快速地干很多数据结构不能干的事情. 很久以前就听说过并且略微地学了一些,但是当时了解地并不是很多. 有些人把splay达成spaly叫做死吧你,(⊙﹏⊙)b 结构 实质上他是一个二叉搜索树,就是每个节点的左儿子在原序列中是在自己左边的,右儿子在原序列中是在自己右边的,构图的方式有很多. 每一个节点都可以存储一些值,表示它的子树中的信息(比如说什么最大值啊,和啊之内的). 构图 fo(i,1,n){ scanf("%d",&

bzoj4825 [Hnoi2017]单旋

4825: [Hnoi2017]单旋 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 520  Solved: 247[Submit][Status][Discuss] Description H 国是一个热爱写代码的国家,那里的人们很小去学校学习写各种各样的数据结构.伸展树(splay)是一种数据 结构,因为代码好写,功能多,效率高,掌握这种数据结构成为了 H 国的必修技能.有一天,邪恶的"卡"带着 他的邪恶的"常数"

【BZOJ4825】[Hnoi2017]单旋 线段树+set

[BZOJ4825][Hnoi2017]单旋 Description H 国是一个热爱写代码的国家,那里的人们很小去学校学习写各种各样的数据结构.伸展树(splay)是一种数据结构,因为代码好写,功能多,效率高,掌握这种数据结构成为了 H 国的必修技能.有一天,邪恶的“卡”带着他的邪恶的“常数”来企图毁灭 H 国.“卡”给 H 国的人洗脑说,splay 如果写成单旋的,将会更快.“卡”称“单旋 splay”为“spaly”.虽说他说的很没道理,但还是有 H 国的人相信了,小 H 就是其中之一,s

BZOJ4825:[HNOI2017]单旋

4825: [Hnoi2017]单旋 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 550  Solved: 258[Submit][Status][Discuss] Description H 国是一个热爱写代码的国家,那里的人们很小去学校学习写各种各样的数据结构.伸展树(splay)是一种数据 结构,因为代码好写,功能多,效率高,掌握这种数据结构成为了 H 国的必修技能.有一天,邪恶的“卡”带着 他的邪恶的“常数”来企图毁灭 H 国.“卡”给

[HNOI 2017]单旋

Description H 国是一个热爱写代码的国家,那里的人们很小去学校学习写各种各样的数据结构.伸展树(splay)是一种数据 结构,因为代码好写,功能多,效率高,掌握这种数据结构成为了 H 国的必修技能.有一天,邪恶的“卡”带着 他的邪恶的“常数”来企图毁灭 H 国.“卡”给 H 国的人洗脑说,splay 如果写成单旋的,将会更快.“卡”称 “单旋 splay”为“spaly”.虽说他说的很没道理,但还是有 H 国的人相信了,小 H 就是其中之一,spaly 马 上成为他的信仰. 而 H

[AH2017/HNOI2017]单旋

题目描述 H国是一个热爱写代码的国家,那里的人们很小去学校学习写各种各样的数据结构.伸展树(splay)是一种数据结构,因为代码好写,功能多,效率高,掌握这种数据结构成为了H国的必修技能.有一天,邪恶的"卡"带着他的邪恶的"常数"来企图毁灭H国."卡"给H国的人洗脑说,splay如果写成单旋的,将会更快."卡"称"单旋splay"为"spaly".虽说他说的很没道理,但还是有H国的人相信

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

ORZ原创Clove学姐: 变量声明:f[i]表示i的父结点,ch[i][0]表示i的左儿子,ch[i][1]表示i的右儿子,key[i]表示i的关键字(即结点i代表的那个数字),cnt[i]表示i结点的关键字出现的次数(相当于权值),size[i]表示包括i的这个子树的大小:sz为整棵树的大小,root为整棵树的根. 再介绍几个基本操作: [clear操作]:将当前点的各项值都清0(用于删除之后) inline void clear(int x){ ch[x][0]=ch[x][1]=f[x]

【bzoj3224】Tyvj 1728 普通平衡树 平衡树的三种姿势 :splay,Treap,ScapeGoat_Tree

直接上代码 正所谓 人傻自带大常数 平衡树的几种姿势:  AVL Red&Black_Tree 码量爆炸,不常用:SBT 出于各种原因,不常用. 常用: Treap 旋转 基于旋转操作和随机数堆 但不支持区间操作. 非旋转 基于随机数堆和拆分合并操作 常数较大 Spaly 完全基于旋转 各种操作 ScapeGoat_Tree 基于a权值平衡树和压扁重构 无旋转 但不支持区间操作 PS:非旋转可以实现平衡树的可持久化,从而来套一些东西 splay #include<cstdio> #de

[BZOJ2733] [HNOI2012] 永无乡 (splay启发式合并)

Description 永无乡包含 n 座岛,编号从 1 到 n,每座岛都有自己的独一无二的重要度,按照重要度可 以将这 n 座岛排名,名次用 1 到 n 来表示.某些岛之间由巨大的桥连接,通过桥可以从一个岛 到达另一个岛.如果从岛 a 出发经过若干座(含 0 座)桥可以到达岛 b,则称岛 a 和岛 b 是连 通的.现在有两种操作:B x y 表示在岛 x 与岛 y 之间修建一座新桥.Q x k 表示询问当前与岛 x连通的所有岛中第 k 重要的是哪座岛,即所有与岛 x 连通的岛中重要度排名第 k