iinattr

splay

2022-07-24

(今天本想讲LCT 但本人TCL 所以大概率讲完会的还是会 不会的还是不会 所以将点简单的 巨神们可以去切题了)

引入

想必大家都已经十分熟悉二叉搜索树了 它是很好用的数据结构 但二叉搜索树仍存在问题 即当插入数据单调时 搜索树退化成一条链

这时我们使用一种称为平衡树的数据结构来优化它

想必大家也已经从那名为ZYX的存在对那AVL的神圣解释中对此十分熟悉啦

带大家复习一下伸展树

功能

伸展树作为一种平衡树其时间复杂度并不优秀 但因应用多样而在算法竞赛中广泛使用 较为重要

支持二叉搜索树全部功能 并因可以无损变换树形结构 是实现动态树(LCT)的基础

整个过程源于二叉搜索树的实际应用特点:当访问一个数据后大概率还会很快再次访问此数据 所以可以当每次对数据一个操作后 将其旋转到根

但这样的特点并不普遍(比如毒瘤出题人的毒瘤数据)

不过在将其旋转到根的过程中 可以实现一平衡操作 即得splay

实现

核心是splay操作 即将一点旋转到根

splay操作由rotate操作恰当组合而成

rotate操作即将一节点与其父子关系反向且仍保持二叉查找树性质

也就是一般常说的单旋操作

(让我现场画图qwq)

splay由双rotate旋组成

如果一节点的父亲与其父亲的父亲在一条直线上 则先rotate其父亲 再rotate其自身

否则两次rotate其自身

为什么要这样呢qwq

显而易见

(让我画个图解释一下)

其余操作非核心 边逐行大声朗读代码边说

例题P3369普通平衡树https://www.luogu.com.cn/problem/P3369

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#include<iostream>
using namespace std;
int siz[100001],ch[100001][2];
int fa[100001],cnt[100001]
,val[100001];
int root=0,N,tot=0;
void pushup(int x)
{
siz[x]=siz[ch[x][0]]+siz[ch[x][1]]+cnt[x];
}
int son(int x)
{
if(x==ch[fa[x]][0]) return 0;
else return 1;
}
void rotate(int x)
{
int y=fa[x];
int zch=ch[x][!son(x)];
int gr=fa[y];
bool yy=son(y),xx=son(x);
ch[gr][yy]=x;fa[x]=gr;
ch[x][!xx]=y;fa[y]=x;
ch[y][xx]=zch;fa[zch]=y;
pushup(y);pushup(x);
}
int splay(int x,int y)
{
while(fa[x]!=y)
{
int fat=fa[x];
int z=fa[fat];
if(z==y)
rotate(x);
else if(son(fat)==son(x))
{
rotate(fat);
rotate(x);
}
else
{
rotate(x);
rotate(x);
}
}
if(y==0) root=x;
}
void insert(int x)
{
int u=root,ff=0;
while(u&&val[u]!=x)
{
ff=u;
u=ch[u][x>val[u]];
}
if(u) cnt[u]++;
else
{
u=++tot;
if(ff)
ch[ff][x>val[ff]]=u;
ch[tot][0]=0;
ch[tot][1]=0;
fa[tot]=ff; val[tot]=x;
cnt[tot]=1;
}
splay(u,0);
}
void find(int x)
{
int u=root;
if(!u) return;
while(ch[u][x>val[u]]&&x!=val[u])
u=ch[u][x>val[u]];
splay(u,0);
}
int next(int x,int f)
{
find(x);
int u=root;
if(val[u]>x&&f||val[u]<x&&!f) return u;
u=ch[u][f];
while(ch[u][f^1]) u=ch[u][f^1];
return u;
}
void delet(int x)
{
int llast=next(x,0);
int nnext=next(x,1);
splay(llast,0);
splay(nnext,llast);
int del=ch[nnext][0];
if(cnt[del]>1)
cnt[del]--,splay(del,0);
else ch[nnext][0]=0;
}
int kth(int x)
{
int u=root;
if(siz[u]<x) return 0;
while(1)
{
int y=ch[u][0];
if(x>siz[y]+cnt[u])
{
x-=siz[y]+cnt[u];
u=ch[u][1];
}
else
if(siz[y]>=x)
u=y;
else
return val[u];
}
}
int main()
{
insert(-2147483647);
insert(2147483647);
cin>>N;
while(N--)
{
int opt;
cin>>opt;
if(opt==1)
{
int ppp;
cin>>ppp;
insert(ppp);
}
else
if(opt==2)
{
int ppp;
cin>>ppp;
delet(ppp);
}
else
if(opt==3)
{
int ppp;
cin>>ppp;
find(ppp);
cout<<siz[ch[root][0]]<<endl;
}
else
if(opt==4)
{
int ppp;
cin>>ppp;
cout<<kth(ppp+1)<<endl;
}
else
if(opt==5)
{
int ppp;
cin>>ppp;
cout<<val[next(ppp,0)]<<endl;
}
else
if(opt==6)
{
int ppp;
cin>>ppp;
cout<<val[next(ppp,1)]<<endl;
}
}
return 0;
}

文艺平衡树

思路:1.提取区间

​ 2.打tag翻转

(让我画个图qwqwqw)

$T_n=k*T_{n-1}+n$