【BZOJ 3224】普通平衡树-Splay
来源:互联网 发布:php 统计系统 编辑:程序博客网 时间:2024/05/21 21:41
之前用SBT做过一次,splay的操作都快忘记了所以拿这道题练练手。
splay支持的操作和SBT一样,只不过多出了一个splay操作,所以写法上会有一些不同,并且splay相比SBT更加能够应付极端数据。
基本操作
定义结构体
这次用了压缩的写法,tme用来存当前节点出现的次数,size用来存当前子树的大小。话说这两个东西是可以互相转化的但是为了后面方便(个屁)所以这么写。
struct TREE{ int ftr,son[3],key,tme,size;} tree[N];
旋转
splay的旋转和SBT的旋转是一样的,只不过这里把左旋和右旋写在了一起。
PS讲道理应该是0表示左孩子1表示右孩子这样就可以位运算啦但是我太菜了所以1表示左孩子2表示右孩子。
然后之前的那个压缩写法之所以这么短是因为下面调用了maintain过程,这里为了理解方便就不这么写了。
这里维护的东西有点多,但是本质上还是一样的,以后找个简单点的模板改一下。
void rotate(int x,int w)//w=1左旋,w=2右旋{ int y = tree[x].ftr; tree[y].size = tree[y].size - tree[x].size + tree[tree[x].son[w]].size; tree[x].size = tree[x].size - tree[tree[x].son[w]].size + tree[y].size; tree[y].son[3-w] = tree[x].son[w]; if (tree[x].son[w] != 0) tree[tree[x].son[w]].ftr = y; tree[x].ftr = tree[y].ftr; if (tree[y].ftr != 0) if (y == tree[tree[y].ftr].son[1]) tree[tree[y].ftr].son[1] = x; else tree[tree[y].ftr].son[2] = x; tree[y].ftr = x; tree[x].son[w] = y;}
双旋
有了旋转(单旋)的基础,我们就可以定义一个splay独有的操作——双旋。在这里,我们将左旋记为Zig,右旋记为Zag。
通过这样不断地旋转,就可以将目标节点转到根节点。
Zig-Zig与Zag-Zag
Zig-Zig与Zag-Zag通过两次同方向旋转,适用于目标节点及其父节点都是左孩子(左图)或都是右孩子(右图)。
注意:这里的双旋,是先对父节点旋转再对自己旋转!
Zig-Zag与Zag-Zig
Zig-Zag与Zag-Zig通过两次不同方向旋转,适用于目标节点及其父节点分别在不同侧。
注意:这里的双旋,都是对自己旋转!
//其中定义:#define ZIG rotate(x,2);#define ZAG rotate(x,1);#define ZIGZIG {rotate(y,2); rotate(x,2);}#define ZAGZIG {rotate(x,1); rotate(x,2);}#define ZAGZAG {rotate(y,1); rotate(x,1);}#define ZIGZAG {rotate(x,2); rotate(x,1);} void splay(int x){ while (tree[x].ftr != 0) { int y = tree[x].ftr; if (tree[y].ftr == 0) if (x == tree[y].son[1]) ZIG else ZAG else if (tree[tree[y].ftr].son[1] == y) if (x == tree[y].son[1]) ZIGZIG else ZAGZIG else if (x == tree[y].son[2]) ZAGZAG else ZIGZAG } root = x;}
双旋的复杂度证明
终于等到了这里!双旋复杂度!
之前SBT因为其优秀的严格平衡性质(也就是不断地maintain维护)可以保证其复杂度为logn,但是在splay中并没有maintain这一操作,而旋转操作的目标仅仅是将目标节点转至根节点,那么splay是如何保证其logn的复杂度呢?
在双旋中我们注意到:
Zig-Zig与Zag-Zag是先对父节点旋转再对自己旋转!
Zig-Zag与Zag-Zig都是对自己旋转!
那么问题就出在双旋上。我们想象一下如果只定义单旋会怎么样?每次只要判断是左孩子和右孩子,对应用Zig或Zag就好了,是不是简单很多?
但是手工模拟一下就会发现Zig-Zig操作如果都对自己旋转,结果就是树高不再稳定!
是的的确双旋需要判断的东西有点多,但是这也是为了效率考虑,虽然一般不会来卡splay,但万一呢?
双旋的话tarjan证明了复杂度均摊为O(logn)。单旋的话就是妥妥O(n)了啊,虽然不卡的话很快,但是单旋比较好写。如果不太记得双旋怎么写的话可以写单旋,一般能过,但是要注意千万不要用单旋去写LCT,因为一条链就可以卡掉(눈_눈我就这么狗带的)如果会双旋最好用双旋咯!信Tarjan得永生⊙ω⊙
from:尛焱轟
https://www.zhihu.com/question/40777845/answer/88181917
好的那就双旋吧。。。。
搜索
这里还要定义一个恶心的搜索,然后这就会牵扯到一个非常严肃的问题。。那就是为什么要用搜索!
在SBT中我一直习惯用递归,因为递归写法加上标记回传代码非常简洁。在写splay的时候我也是习惯性地写成了递归,
但是但是但是!重要的事情说三遍!
在每次操作后(所有操作)都要调用一次splay,也就是说把出现频率高的节点向上移动,但是这样就改变了整棵树的形态,这就导致回溯的时候许多信息都是错误的。。。
我卡了好久!
害得我现在的代码又丑又丑又丑。。。
int search(int x,int key){ int r = x; while (tree[r].key != key) { if (key < tree[r].key) { if (tree[r].son[1] == 0) break; r = tree[r].son[1]; } else { if (tree[r].son[2] == 0) break; r = tree[r].son[2]; } } return r;}
插入
对没错就是插入!我就是用递归插入!
先上不用递归的代码(加了初始节点特判我也不知道怎么写在一起干脆就这样了):
void insert(int x) { bool flag; int u; if (tot == 0) { tot = 1; tree[1].ftr = 0; tree[1].key = x; tree[1].size = tree[1].tme = root = 1; return; } int k = search(root,x); if (tree[k].key == x) { tree[k].tme++; u = k; flag = true; } else { tot++; tree[tot].key=x; tree[tot].ftr=k; tree[tot].size = tree[tot].tme = 1; if (tree[k].key > x) tree[k].son[1] = tot; else tree[k].son[2] = tot; flag = false; } while (k > 0) { tree[k].size++; k = tree[k].ftr; } if (flag) splay(u); else splay(tot); }
然后之前的画风是这样的。。没有斜杠的。。
void insert(int &x,int key,int last){ if (x == 0) { x = ++tot; tree[x].son[1] = tree[x].son[2] = 0; tree[x].size = tree[x].tme = 1; tree[x].key = key; tree[x].ftr = last; //splay(x); } else { tree[x].size++; if (key < tree[x].key) insert(tree[x].son[1],key,x); if (key > tree[x].key) insert(tree[x].son[2],key,x); //if (key == tree[x].key) {tree[x].tme++; splay(x);} if (key == tree[x].key) tree[x].tme++; }}
删除
没错删除也不能按照原来的方法删除。。。
void remove(int x,int key){ if(key > tree[x].key) remove(tree[x].son[2],key); else if(key < tree[x].key) remove(tree[x].son[1],key); else { splay(x); if (tree[x].tme > 1) {tree[x].tme--; tree[x].size--;} else if (tree[x].son[1] == 0) { int y = tree[x].son[2]; tree[x].son[2] = tree[x].size = tree[x].key = tree[x].tme = 0; root = y; tree[root].ftr = 0; } else { tree[tree[x].son[1]].ftr = 0; int y = extreme(tree[x].son[1],MMAX); tree[root].son[2] = tree[x].son[2]; tree[root].size = tree[root].size + tree[tree[x].son[2]].size; if (tree[root].son[2] != 0) {tree[tree[root].son[2]].ftr = root;} tree[x].son[1] = tree[x].son[2] = tree[x].size = tree[x].key = tree[x].tme = 0; } }}
大概解释一下就是,每次把要删除的节点转到根,然后删掉,如果没有左子树,那么直接把右子树连上去,否则找到左子树的最大值,转到根,把右子树连到这个最大值上面(之前最大值一定没有右子树)。其实想想挺简单,但是结构体东西比较多所以比较乱。
第k值&查排名
直接套的SBT,基本没改。
int GetKth(int x,int k){ int s1 = tree[tree[x].son[1]].size; int s2 = tree[x].size - tree[tree[x].son[1]].size - tree[tree[x].son[2]].size; if (s1+1<=k&&k<=s1+s2) {int p = tree[x].key; splay(x); return p;} if(k > s1+s2) return GetKth(tree[x].son[2],k - s1 - s2); else return GetKth(tree[x].son[1],k); } int GetRank(int x,int key){ int s1 = tree[tree[x].son[1]].size; int s2 = tree[x].size - tree[tree[x].son[1]].size - tree[tree[x].son[2]].size; if (key == tree[x].key) { int p = 0; if (tree[x].son[1] == 0) p = 1; else p = s1+1; splay(x); return p; } if(key < tree[x].key) return GetRank(tree[x].son[1],key); if(key > tree[x].key) return GetRank(tree[x].son[2],key)+s1+s2; }
前驱和后继
终于到了最激动人心的时刻,展现splay伟大技巧的时候到了!
这里只以前驱为例。
每次查询x的前驱,首先插入x以免树中没有x,然后把x转到根,这时候左子树的最大值就是前驱!是不是很简单!
后继同理。
int pred(int key){ insert(key); int k = search(root,key); splay(k); int res = extreme(tree[k].son[1],1); remove(root,key); return res;} int succ(int key){ insert(key); int k = search(root,key); splay(k); int res = extreme(tree[k].son[2],2); remove(root,key); return res;}
完整代码
#include<cmath>#include<cstdio>#include<vector>#include<cstring>#include<iomanip>#include<stdlib.h>#include<iostream>#include<algorithm>#define ll long long#define inf 1000000000#define mod 1000000007#define N 3000000#define ZIG rotate(x,2);#define ZAG rotate(x,1);#define ZIGZIG {rotate(y,2); rotate(x,2);}#define ZAGZIG {rotate(x,1); rotate(x,2);}#define ZAGZAG {rotate(y,1); rotate(x,1);}#define ZIGZAG {rotate(x,2); rotate(x,1);} #define MMAX 1#define MMIN 2using namespace std;struct TREE{ int ftr,son[3],key,tme,size;} tree[N];int root,tot,n,opt,i,x;void rotate(int x,int w)//w=1左旋,w=2右旋{ int y = tree[x].ftr; tree[y].size = tree[y].size - tree[x].size + tree[tree[x].son[w]].size; tree[x].size = tree[x].size - tree[tree[x].son[w]].size + tree[y].size; tree[y].son[3-w] = tree[x].son[w]; if (tree[x].son[w] != 0) tree[tree[x].son[w]].ftr = y; tree[x].ftr = tree[y].ftr; if (tree[y].ftr != 0) if (y == tree[tree[y].ftr].son[1]) tree[tree[y].ftr].son[1] = x; else tree[tree[y].ftr].son[2] = x; tree[y].ftr = x; tree[x].son[w] = y;}int search(int x,int key){ int r = x; while (tree[r].key != key) { if (key < tree[r].key) { if (tree[r].son[1] == 0) break; r = tree[r].son[1]; } else { if (tree[r].son[2] == 0) break; r = tree[r].son[2]; } } return r;}void splay(int x){ while (tree[x].ftr != 0) { int y = tree[x].ftr; if (tree[y].ftr == 0) if (x == tree[y].son[1]) ZIG else ZAG else if (tree[tree[y].ftr].son[1] == y) if (x == tree[y].son[1]) ZIGZIG else ZAGZIG else if (x == tree[y].son[2]) ZAGZAG else ZIGZAG } root = x;}void insert(int x) { bool flag; int u; if (tot == 0) { tot = 1; tree[1].ftr = 0; tree[1].key = x; tree[1].size = tree[1].tme = root = 1; return; } int k = search(root,x); if (tree[k].key == x) { tree[k].tme++; u = k; flag = true; } else { tot++; tree[tot].key=x; tree[tot].ftr=k; tree[tot].size = tree[tot].tme = 1; if (tree[k].key > x) tree[k].son[1] = tot; else tree[k].son[2] = tot; flag = false; } while (k > 0) { tree[k].size++; k = tree[k].ftr; } if (flag) splay(u); else splay(tot); } int extreme(int x,int w) { int k,tmp; k = x; while (tree[k].son[3-w] != 0) k = tree[k].son[3-w]; tmp = tree[k].key; splay(k); return tmp; } void remove(int x,int key){ if(key > tree[x].key) remove(tree[x].son[2],key); else if(key < tree[x].key) remove(tree[x].son[1],key); else { splay(x); if (tree[x].tme > 1) {tree[x].tme--; tree[x].size--;} else if (tree[x].son[1] == 0) { int y = tree[x].son[2]; tree[x].son[2] = tree[x].size = tree[x].key = tree[x].tme = 0; root = y; tree[root].ftr = 0; } else { tree[tree[x].son[1]].ftr = 0; int y = extreme(tree[x].son[1],MMAX); tree[root].son[2] = tree[x].son[2]; tree[root].size = tree[root].size + tree[tree[x].son[2]].size; if (tree[root].son[2] != 0) {tree[tree[root].son[2]].ftr = root;} tree[x].son[1] = tree[x].son[2] = tree[x].size = tree[x].key = tree[x].tme = 0; } }}int GetKth(int x,int k){ int s1 = tree[tree[x].son[1]].size; int s2 = tree[x].size - tree[tree[x].son[1]].size - tree[tree[x].son[2]].size; if (s1+1<=k&&k<=s1+s2) {int p = tree[x].key; splay(x); return p;} if(k > s1+s2) return GetKth(tree[x].son[2],k - s1 - s2); else return GetKth(tree[x].son[1],k); } int GetRank(int x,int key){ int s1 = tree[tree[x].son[1]].size; int s2 = tree[x].size - tree[tree[x].son[1]].size - tree[tree[x].son[2]].size; if (key == tree[x].key) { int p = 0; if (tree[x].son[1] == 0) p = 1; else p = s1+1; splay(x); return p; } if(key < tree[x].key) return GetRank(tree[x].son[1],key); if(key > tree[x].key) return GetRank(tree[x].son[2],key)+s1+s2; } int pred(int key){ insert(key); int k = search(root,key); splay(k); int res = extreme(tree[k].son[1],1); remove(root,key); return res;} int succ(int key){ insert(key); int k = search(root,key); splay(k); int res = extreme(tree[k].son[2],2); remove(root,key); return res;} int main(){ scanf("%d",&n); for (i = 1;i <= n; i++) { scanf("%d%d",&opt,&x); if (opt == 1) insert(x); if (opt == 2) remove(root,x); if (opt == 3) printf("%d\n",GetRank(root,x)); if (opt == 4) printf("%d\n",GetKth(root,x)); if (opt == 5) printf("%d\n",pred(x)); if (opt == 6) printf("%d\n",succ(x)); }}
- 【splay】BZOJ 3224 普通平衡树
- 【BZOJ 3224】普通平衡树-Splay
- bzoj 3224==tyvj 1728普通平衡树 splay
- bzoj 3224 Tyvj 1728 普通平衡树 [Splay]
- bzoj 3224: Tyvj 1728 普通平衡树 (Splay模板)
- [bzoj 3224] Tyvj 1728 普通平衡树(Splay)
- BZOJ 3224: Tyvj 1728 普通平衡树 [Splay]【数据结构】
- BZOJ 3224 Tyvj 1728 普通平衡树 Splay
- BZOJ 3224 Tyvj 1728 普通平衡树 (Splay)
- splay(普通平衡树)
- [lydsy] 3224 普通平衡树 [Splay]
- bzoj 3224: Tyvj 1728 普通平衡树(splay 模板题)
- [BZOJ 3224]普通平衡树(忽然想要存个模板 Treap/Splay)
- bzoj 3224 普通平衡树
- bzoj 3224 普通平衡树
- BZOJ 3224, 普通平衡树
- BZOJ 3224 普通平衡树
- BZOJ 3224 普通平衡树
- Android中EditText的使用总结
- 静态代码块、代码块、构造执行顺序
- Javascript变量与函数的声明与提升
- 动态初始化下拉框以及修改操作时的下拉框赋值
- opencv 学习之检测角点
- 【BZOJ 3224】普通平衡树-Splay
- Glide-加载本地图片
- 1035. 插入与归并(25)
- 非继承类的复用
- 开始写博客的计划
- Java基础之Swing图形界面聊天窗口
- 面试题16:反转链表
- TensorFlow学习笔记(1):使用softmax对手写体数字(MNIST数据集)进行识别
- PHP数组几种常见的排序方式