学习了一下splay。
splay主要就是干线段树干的活,外加区间翻转,插入删除,还有分裂合并。
建树
就是普通的建成二叉树,为了方便以后的区间操作,会在两边建两个辅助的点。
int build(int l,int r,int y) // [l,r],父亲是y
{
if(l > r) return 0;
int mid = (l + r) / 2;
int x = insert(a[mid]); f[x] = y; //建当前节点,insert是插入节点函数
if(l == r) return x;
tt[x][0] = build(l,mid - 1,x);
tt[x][1] = build(mid + 1,r,x);
pushup(x);//和线段树一样吧
return x;
}
insert 函数
int insert(int key){
int o;
if(shan[0] > 0) o = shan[shan[0]--];
else o = tot++;
//因为splay有删除操作,所以要建一个栈(shan)来收容以前删除的点,重复利用
...
//这里视题目而定,对点进行初始化
return 0;
}
splay 和 treap的不同主要是splay一般是把要操作的区间旋上去进行操作。而treap则给每个点加修正值,维护成堆的结构,来保证复杂度。所以splay最核心的就是splay操作了呗。
介绍splay操作就得先介绍rotate,旋转操作,和普通的平衡二叉树的旋转没有区别。
int son(int x){
if(tt[f[x]][0] == x) return 0;else return 1;
//这个函数用来判断x是父亲的哪个儿子
}
void rotate(int x){
int y = f[x],z = son(x);
//三步走
tt[y][z] = tt[x][1 - z];
if(tt[x][1 - z]) f[tt[x][1 - z]] = y;
//先是把x的儿子接到y上
f[x] = f[y];
if(f[y]) tt[f[y]][son(y)] = x;
//把x接到y的父亲上
f[y] = x;
tt[x][1 - z] = y;
//y接到x上
pushup(y),pushup(x);//转完要维护信息
return;
}
void splay(int x,int y){//把x旋到y的子树上面去
if(x == y) return;
update(x,y);//由于x要旋转上去,所以要先把标记理一理
while(f[x] != y){
if(f[f[x]] != y){
if(son(f[x]) == son(x)) rotate(f[x]);
else rotate(x);//和普通avl树的旋转一样
}
rotate(x);
}
if(y == 0) root = x;
}
pushup和pushdown函数和线段树的并无二致。
del函数
void del(int x){
if(x == 0) return;//空树不删了
shan[++shan[0]] = x;
del(tt[x][0]);del(tt[x][1]);
}
update函数
void update(int x,int y){
do{
d[++d[0]] = x;
x = f[x];
}while(x != y);
while(d[0]) pushdown(x);
}//因为要从上往下pushdown,所以要用栈从下往上存起来
对区间[l,r]进行操作,先把l - 1旋到根节点,再把r + 1旋到l - 1的右儿子,那么[l,r]在r + 1的左儿子,该打标记打标记,该删删,该加加。
int x = kth(root,l - 1);splay(x,0);
int y = kth(root,r + 1);splay(y,x);
//balabala操作
其中kth操作就是二叉树找第k个节点。
分裂和合并
有的时候让把哪一段取出来放到前面还是后面,就要分裂和合并。
void split(int root,int k,int &l,int &r){
int x = kth(root,k);splay(x,0);
l = x;
r = tt[x][1];
f[r] = 0;
tt[x][1] = 0;update(l);
return;
}
void merge(int root1,int root2,int & newroot){
int x = kth(root1,size[root1]);
splay(x,0);
t[x][1] = root2;
f[root2] = x;
update(root1);
newroot = root1;
}
例题就是bzoj1500