蒟蒻的平衡树学习笔记
动态更新,在整理QWQ
平衡树主要用来动态维护一些在序列上的操作问题
通过旋转操作使树平衡,且保持中序遍历结果不变,即左小右大的结构,便于在log的时间内访问节点
本文主要使用splay, 一种使用范围较为广泛,方便理解(划掉)的数据结构
(其实还有更简单的,比如说vector+二分,树状数组+二分,map,pbds(noip禁用)等等,但是并不泛用)
当然,替罪羊树,红黑树,各种treap, 各种树套各种树也不是不行 ( 只是作者菜不会
下面分步介绍平衡树这一数据结构 ( 较为详细
1 . 开树的结构体
树本文一般用结构体表示,较为清晰
主要有以下几个变量
struct splay{ int tag; //懒标记 int cnt; //记录某个权值出现的次数 int sz; //记录这个节点以下子树的大小 int ch[2]; //ch[0]表示左节点,ch[1]表示右节点
//这样便于访问(后面会有很大帮助,能减少讨论各种情况,缩短代码长度 int fa; //记录这个节点的父亲 int val; //记录这个节点的权值 int ******** //根据题目的具体要求自信定义 }T[1001010];
2.update操作
//update操作主要用来把左右子节点的大小信息传上去,更新各种旋转操作后子树的大小信息 //代码很短,意思是当前节点的子树,等于左儿子的子树大小加上右儿子的子树大小
//再加上自己节点数的个数cnt。 int update(int x) { T[x].sz = T[T[x].ch[0]].sz + T[T[x].ch[1]].sz + T[x].cnt; }
细节:没啥细节的,有的题目cnt是1(因为不会出现重复元素),记得改成1就好了(
3.wh操作
//wh操作是用来询问当前节点为父亲节点的左节点还是右节点 bool wh(int x) { return T[T[x].fa].ch[1] == x; } //1是右节点,如果x 等于他的父亲的右儿子,即T[T[x].fa].ch[1],等于x就会返回真,也就是1
//这时说明这个节点的是他父亲的右儿子(废话),否则就是左儿子ch[0]
细节:也没啥细节的(, 记得一定是ch[1] 就完事了(
4.pushdown操作
//pushdown操作主要用来下放懒标记的 //这里只有取反操作
void pushdown(int x) { if(T[x].tag) { T[T[x].ch[0]].tag ^= 1, T[T[x].ch[1]].tag ^= 1, T[x].tag = 0; //如果这个节点有懒标记,就把左子树和右子树取反,把原标记赋值为0 swap (T[x].ch[0], T[x].ch[1]); //然后交换左右两个节点 } }
细节:记得先判是否有tag,最后要清空 (线段树的痛现在别犯了QWQ
5.rotate操作
//接下去是最重要的rotate操作了 //通过旋转操作,既可以维护splay的平衡,同时又不破坏遍历后结果的操作, //为了方便画图理解,我们把左右子树称为方向(就是左右两个方向) //主要有三步(以下操作建议按照描述手动画个图会比较好理解 //1.连接这个节点的fa和这个节点所属方向(d1)相反方向的节点,连在这个fa的d1方向上 //2. 连接这个节点的fa和自己,连在这个节点的d1的相反方向上 //3. 直接把这个节点连在grandfather上,方向是d2(就是fa的子节点方向 //因为这样旋转会改变各个节点往下的子树大小啊,所以我们要更新节点的子树大小信息 void rotate(int x) { int fa = T[x].fa, gf = T[fa].fa; int d1 = wh(x), d2 = wh(fa); cont(T[x].ch[d1 ^ 1], fa, d1); cont(fa, x, d1 ^ 1); cont(x, gf, d2); update(fa), update(x); }
细节1: cont连的顺序可以颠倒,但是一定不能打错,最好背下来(
细节2:update的时候一定是先更新fa再更新x,这样才不会互相影响
6 . splay操作
void splay(int x, int goal) { //将x的fa一路旋转到goal while (T[x].fa != goal) { int fa = T[x].fa, gf = T[fa].fa; if (gf != goal) (wh(x) == wh(fa)) ? rotate(fa) : rotate(x); //如果x和fa方向相同,就旋转fa,否则只能旋转x rotate(x); //旋转到gf等于goal的时候,再把x旋转上去 } if (! goal) root = x; //记得特判, 没有父亲的就是根(无父即为王)
}
细节:记得特判
7.inset操作
//这个insert操作比较好理解 //cnt是记录元素出现个数的,然后先一路根据左小右大关系while到目标的x
//如果这个数已经存在了,就直接计数器++,否则新开一个节点 //(有时候这里x是等于rubbish(),rubbish函数是用来给x分配新节点的,后面会讲(一般空间够的话就用不到) //然后创建一个新节点 void insert(int v) { int x = root, fa = 0; while (x && T[x].val != v) fa = x, x = T[x].ch[T[x].val < v]; if (x) T[x].cnt ++; else { x = ++ cnt; if (fa) T[fa].ch[T[fa].val < v] = x; //特判fa是根的情况 T[x].fa = fa, T[x].sz = 1, T[x].cnt = 1, T[x].val = v; } splay(x, 0); //记得把x转上去!!否则一直插可能插出一条链,树就退化了,这里打错可能不会错,但是会莫名其妙t飞!!! }
8.find函数
//find函数主要用来找到权值为v的元素的位置,返回并输出 //代码类似前面的insert函数 int find(int v) { int x = root; while (T[x].val != v && T[x].ch[T[x].val < v]) x = T[x].ch[T[x].val < v]; return x; }
9.getrank函数
//find该节点,旋转到根后输出左子树的权值就好 int getrank(int x) { splay(find(x),0); return T[T[root].ch[0]].sz; }
10.getpre函数
//k == 0 找前驱,k == 1 找后继 //将该节点旋转到根后返回左子树最右边的节点,否则返回右子树最左边的节点
int getpre(int v, int k) { splay(find(v), 0); //将最大小于等于v的节点splay到根 int x = root; if (!k && T[x].val < v) return x; if (k && T[x].val > v) return x; x = T[x].ch[k]; while (T[x].ch[k ^ 1]) x = T[x].ch[k ^ 1]; return x; }
11.删除节点
//删除节点 //记下当前节点的前驱和后继 //把lt翻到根,把后继也翻上去 //把计数器减减后再翻上去 void del(int v) { int lt = getpre(v, 0); int ne = getpre(v, 1); splay (lt, 0), splay (ne, lt); if (T[T[ne].ch[0]].cnt > 1) T[T[ne].ch[0]].cnt --, splay(T[ne].ch[0],0); else T[ne].ch[0] = 0; }
细节:把前驱翻上去后,这是后继应该接到前驱上,而非根上
12.根据排名找权值
//从根开始遍历,往下递归 //记得先pushdown更新,pushdown后的才是准确的值 //因为左小右大,所以每次如果子树比排名小的话,扣掉左子树大小+当前节点计数器,否则再往左儿子走,直到子树大小比排名小 //最后当rk==0的时候,返回x的权值就好了 int getnum(int rk) { int x = root; while (1) { pushdown(x); int ls = T[x].ch[0], rs = T[x].ch[1]; if (rk <= T[ls].sz) x = ls; else if (rk > T[ls].sz + T[x].cnt) rk -= T[ls].sz + T[x].cnt, x = rs; else return T[x].val; } }
接下去就是喜闻乐见的完整代码了(
//普通平衡树,完整代码
#include<bits/stdc++.h> #define rqtql cout << "rqtql" << endl using namespace std; const int inf = 2147483647; struct st { int tag, cnt, sz, ch[2], fa, val; }T[1001010]; int root, cnt, n; int update(int x) { T[x].sz = T[T[x].ch[0]].sz + T[T[x].ch[1]].sz + T[x].cnt; } bool wh(int x) { return T[T[x].fa].ch[1] == x; } void cont(int x, int fa, int h) { T[fa].ch[h] = x, T[x].fa = fa; } void pushdown(int x) { if(T[x].tag) { T[T[x].ch[0]].tag ^= 1, T[T[x].ch[1]].tag ^= 1, T[x].tag = 0; swap (T[x].ch[0], T[x].ch[1]); } } void rotate(int x) { int fa = T[x].fa, gf = T[fa].fa; int s1 = wh(x), s2 = wh(fa); cont(T[x].ch[s1 ^ 1], fa, s1); cont(fa, x, s1 ^ 1); cont(x, gf, s2); update(fa), update(x); } void splay(int x, int goal) { while (T[x].fa != goal) { int fa = T[x].fa, gf = T[fa].fa; if (gf != goal) (wh(x) == wh(fa)) ? rotate(fa) : rotate(x); rotate(x); } if (! goal) root = x; } void insert(int v) { int x = root, fa = 0; while (x && T[x].val != v) fa = x, x = T[x].ch[T[x].val < v]; if (x) T[x].cnt ++; else { x = ++ cnt; if (fa) T[fa].ch[T[fa].val < v] = x; T[x].fa = fa, T[x].sz = 1, T[x].cnt = 1, T[x].val = v; } splay(x, 0); } int find(int v) { int x = root; while (T[x].val != v && T[x].ch[T[x].val < v]) x = T[x].ch[T[x].val < v]; return x; } //find该节点,旋转到根后输出左子树的权值就好 int getrank(int x) { splay(find(x),0); return T[T[root].ch[0]].sz; } //k == 0 找前驱,k == 1 找后继 //将该节点旋转到根后返回左子树最右边的节点,否则返回右子树最左边的节点 int getpre(int v, int k) { splay(find(v), 0); //将最大小于等于v的节点splay到根 int x = root; if (!k && T[x].val < v) return x; if (k && T[x].val > v) return x; x = T[x].ch[k]; while (T[x].ch[k ^ 1]) x = T[x].ch[k ^ 1]; return x; } int getnum(int rk) { int x = root; while (1) { pushdown(x); int ls = T[x].ch[0], rs = T[x].ch[1]; if (rk <= T[ls].sz) x = ls; else if (rk > T[ls].sz + T[x].cnt) rk -= T[ls].sz + T[x].cnt, x = rs; else return T[x].val; } } void del(int v) { int lt = getpre(v, 0); int ne = getpre(v, 1); splay (lt, 0), splay (ne, lt); if (T[T[ne].ch[0]].cnt > 1) T[T[ne].ch[0]].cnt --, splay(T[ne].ch[0],0); else T[ne].ch[0] = 0; } int main() { cin >> n; insert(- inf), insert(inf); for (int i = 1, p, x; i <= n; i ++) { scanf("%d %d", &p, &x); if (p == 1) insert (x); if (p == 2) del (x); if (p == 3) printf("%d ", getrank(x)); if (p == 4) printf("%d ", getnum(x + 1)); if (p == 5) printf("%d ", T[getpre(x, 0)].val); if (p == 6) printf("%d ", T[getpre(x, 1)].val); } return 0; }
文艺平衡树代码相似,只要多维护一个序列翻转的tag,每次下传的时候记得翻转就好了
//文艺平衡树 #include<bits/stdc++.h> #define lrqtxdy cout << "lrqtxdy" << endl using namespace std; const int inf = 0x3f3f3f3f; struct st{ int sz, cnt, ch[2], tag, fa, val; }T[1000101]; int root, cnt, n, m, l, r; void update (int x) { T[x].sz = T[T[x].ch[0]].sz + T[T[x].ch[1]].sz + T[x].cnt; } // 更新权值, 当前子树大小等于左边儿子大小加上右边儿子大小加上当前节点 int wh (int x) { return T[T[x].fa].ch[1] == x; } //查询当前节点是左儿子还是右儿子 void con(int x, int fa, int p) { T[fa].ch[p] = x, T[x].fa = fa; } void rotate (int x) { int fa = T[x].fa, gf = T[fa].fa; //fa是当前节点的父亲,gf是父亲的父亲(grandfather) bool s1 = wh(x), s2 = wh(fa); con(T[x].ch[s1 ^ 1], fa, s1); con(fa, x, s1 ^ 1); con(x, gf, s2); //旋转 update(fa), update(x); //更新权值 } void splay(int x, int pos) { for (int i; (i = T[x].fa) != pos; rotate(x)) if (T[i].fa != pos) rotate((wh(x) == wh(i)) ? i : x); if (!pos) root = x; } void insert (int v) { //lrqtxdy; int x = root, fa = 0; while (x && T[x].val != v) fa = x, x = T[x].ch[T[x].val < v]; //跑到这个节点的fa if (x) T[x].cnt ++; //已经存在的话就把这个数的计数器++ else { x = ++ cnt; if (fa) T[fa].ch[T[fa].val < v] = x; T[x].fa = fa, T[x].sz = 1, T[x].cnt = 1, T[x].val = v; } //插入新节点 splay (x, 0); } void pushdown(int x) { if(T[x].tag) { T[T[x].ch[0]].tag ^= 1, T[T[x].ch[1]].tag ^= 1, T[x].tag = 0; swap (T[x].ch[0], T[x].ch[1]); } } //如果当期节点有打标记,把左右儿子换一下 int kth(int rk) { int x = root; while (1) { pushdown(x); //pushdown后再计算,结果才是准确的 int ls = T[x].ch[0], rs = T[x].ch[1]; if (rk <= T[ls].sz) x = ls; //如果排名比左边子树的大小还小的话,找左子树 else if (rk > T[ls].sz + T[x].cnt) rk -= T[ls].sz + T[x].cnt, x = rs; //如果排名比右边子树 + 自己本身数的个数大的话找右边儿子,找右子树 else return x; } } //打印树的中序遍历结果,输出就是答案 int print(int x) { pushdown(x); //记得pushdown if (T[x].ch[0]) print(T[x].ch[0]); if (T[x].val != - inf && T[x].val != inf) printf("%d ", T[x].val); if (T[x].ch[1]) print(T[x].ch[1]); } int main() { cin >> n >> m; insert (- inf), insert (inf); for (int i = 1; i <= n; i ++) insert(i); while (m --) { scanf("%d %d", &l, &r); splay (kth(l), 0), splay (kth(r + 2), kth(l)); T[T[T[root].ch[1]].ch[0]].tag ^= 1; } print(root); return 0; }