平衡树是平衡的二叉树,有很多种,比如:Splay,Treap,fhq Treap,红黑树,替罪羊树,AVL,SBT。种类很多,用法类似。所以这里只介绍Splay和fhq Treap。
Splay
基于rotate和splay操作。可以任意改变形状。
复杂度
均摊每次操作 O ( l o g N ) O(logN) O(logN),但是常数较大。
基本操作
0. 变量定义
int cnt; // 点数
int root; // 根id
int fa[N], ch[N][2]; // 父亲和左右儿子
int val[N], sum[N]; // 每个点记录一些数据,这里以权值和区间和为例
1. rotate
摘抄一张图,来自santiego
看着图再对应一下代码就可以理解了
int son(int u){return ch[fa[u]][1] == u;}
void rotate(int u)
{
int v = fa[u], w = fa[v];
int ws = son(v), vs = son(u);
ch[w][ws] = u;
fa[v] = u;
ch[v][vs] = ch[u][vs^1];
fa[ch[u][vs^1]] = v;
ch[u][vs^1] = v;
fa[u] = w;
push_up(v); push_up(u);
}
1.1
son(u)
:求u是fa[u]的哪个儿子,左儿子返回0,右儿子返回1。
push_up(u)
:更新区间信息
1.2 注意
修改 X X X一定要在最后,因为修改 c h [ D ] [ s o n ( X ) x o r 1 ] ch[D][son(X) \; xor\;1] ch[D][son(X)xor1]和修改 Y Y Y都需要用到他。
其他顺序随意。考场上画张图写出来还是轻松的。
2.splay
void splay(int u, int goal)
{
while (fa[u] != goal){
if (fa[fa[u]] != goal) rotate(son(u) == son(fa[u]) ? fa[u] : u);
rotate(u);
}
if (goal == 0) root = u;
}
双旋。对我这种程度的选手需要记忆,因为理解的话需要会证Splay的均摊复杂度,人家证出来这么旋就是比较快也没有办法。
下面的图来自自为风月马前卒
2.1 一线型
先转父亲再转自己。转完还是一条线(看起来挺不科学的)。
2.2 弯曲型
转两次自己。转完是一个三角形。
总结下来就是:
一线型转父亲!!!
延伸操作
1 find
重要的辅助函数。
找到权值大于等于x的第一个点,并splay到根。
利用二叉查找树的性质,从根开始走一条链去找。
2 k_th
找第k大的数并splay到根。
与find相似,利用二叉查找树性质,从根开始往下爬。
3 insert
在第i个数后面插入一个或者一个序列的数。
find第i个数并splay到根。
在root的右子树中把所有节点都放到右半边,即把find右子树中的第1个数并splay到右子树的根。
这时root的右子树的左子树是空的,把要插的数放在这个位置即可。
4 delete
去掉一个区间的数。
和insert类似。
find两遍,把root的右子树的左子树整个去掉。
5 pre
寻找节点u的前驱。
把u节点splay到根。在左子树中一直往右儿子跑,直到跑不下去了为止,即为前驱。
6 suc
寻找节点u的后继。
与pre相似。
把u节点splay到根。在右子树中一直往左儿子跑,直到跑不下去了为止,即为后继。
7 reverse
翻转一个区间。
区间打标记(区间加减也是同理)。
两遍find把需要操作的区间放到root的右儿子的左儿子的位置,然后打上翻转标记,splay到根。
以后每次find的时候都需要先push_down()把翻转标记推下去。
fhq Treap
持续看心情更新