伸展树(英语:Splay Tree)是一种二叉查找树,它能在O(log n)内完成插入、查找和删除操作。它是由丹尼尔·斯立特(Daniel Sleator)和罗伯特·塔扬在1985年发明的[1]。
在伸展树上的一般操作都基于伸展操作:假设想要对一个二叉查找树执行一系列的查找操作,为了使整个查找时间更小,被查频率高的那些条目就应当经常处于靠近树根的位置。于是想到设计一个简单方法, 在每次查找之后对树进行重构,把被查找的条目搬移到离树根近一些的地方。伸展树应运而生。伸展树是一种自调整形式的二叉查找树,它会沿着从某个节点到树根之间的路径,通过一系列的旋转把这个节点搬移到树根去。
它的优势在于不需要记录用于平衡树的冗余信息。
——来自维基百科
几个常用操作:
其实也不一定对。。。
rotate操作
void rotate(int x,int &k) { int y = f[x], z = f[y], l, r; if (tr[y][0] == x) l = 0; else l = 1; r = l ^ 1; if (y == k) k = x; else { if (tr[z][0] == y) tr[z][0] = x; else tr[z][1] = x; } f[x] = z; f[y] = x; f[tr[x][r]] = y; tr[y][l] = tr[x][r]; tr[x][r] = y; }
把x向上旋转一个
r = l ^ 1; 01取反
splay操作
void splay(int x,int &k) { int y,z; while (x != k) { y = f[x]; z = f[y]; if (y != k) { if ((tr[z][0] == y)^(tr[y][0] == x)) rotate(x,k); else rotate(y,k); } rotate(x,k); } }
把x转到k的位置
y !=k 至少要旋转两次,否则直接旋转一次x就行了
(tr[z][0] == y)^(tr[y][0] == x) 判断在不在一条直线上
如果是的话,先旋转父节点,再旋转自己;否则先旋转自己,再旋转父节点(就是旋转两次自己)
因为调用了rotate,在rotate中k的值改变了,所以在这里也要加&
插入元素
void ins(int &k,int x,int last) { if (k == 0) { size ++; k = size; num[k] = x; f[k] = last; splay(k,rt); return; } if (x < num[k]) ins(tr[k][0],x,k); else ins(tr[k][1],x,k); }
插入一个元素
操作完成后k的值会变成size,size会转到根,所以k的值要返回给rt,所以要加上&
删除元素
void del(int x) { splay(x,rt); if (tr[x][0] * tr[x][1] == 0) rt = tr[x][0] + tr[x][1]; else { int k = tr[x][1]; while (tr[k][0]) k = tr[k][0]; tr[k][0] = tr[x][0]; f[tr[x][0]] = k; rt = tr[x][1]; } f[rt] = 0; }
删除编号为x的结点
先旋转到根,如果只有一棵子树,直接删除,让子树的根作为根节点
否则寻找右子树中最小的那个点,作为根节点
寻找前驱
void ask_before(int k,int x) { if (k == 0) return; if (num[k] <= x) { t1 = k; ask_before(tr[k][1],x); } else ask_before(tr[k][0],x); }
寻找后继
void ask_after(int k,int x) { if (k == 0) return; if (num[k] >= x) { t2 = k; ask_after(tr[k][0],x); } else ask_after(tr[k][1],x); }
建树
void build(int l,int r,int f) { if (l > r) return; int mid = (l+r) >> 1, now = id[mid], last = id[f]; if (l == r) { size[l] = 1; tag[l] = rev[l] = 0; fa[l] = last; if (l < f) c[last][0] = now; else c[last][1] = now; return; } build(l,mid-1,mid); build(mid+1,r,mid); fa[now] = last; updata(now); if (mid < f) c[last][0] = now; else c[last][1] = now; }
id是编号,size是大小
找第rk大的元素
int find(int &k,int rk) { pushdown(k); int l = c[k][0], r = c[k][1]; if (size[l]+1 == rk) return k; if (size[l] >= rk) return find(l,rk); return find(r,rk-size[l]-1); }
区间+(标记)
void add(int l,int r,int v) { int x = find(rt,l), y = find(rt,r+2); splay(x,rt); splay(y,c[x][1]); int z = c[y][0]; tag[z] += v; mx[z] += v; w[z] += v; }
翻转(标记)
void rever(int l,int r) { int x = find(rt,l), y = find(rt,r+2); splay(x,rt); splay(y,c[x][1]); int z = c[y][0]; rev[z] ^= 1; }
查询
int solvemx(int l,int r) { int x = find(rt,l), y = find(rt,r+2); splay(x,rt); splay(y,c[x][1]); int z = c[y][0]; return mx[z]; }
更新
void updata(int x) { int l = c[x][0], r = c[x][1]; size[x] = size[l] + size[r] +1; mx[x] = max(mx[l],mx[r]); mx[x] = max(mx[x],w[x]); }
下传标记
void pushdown(int x) { int l = c[x][0], r = c[x][1]; if (rev[x]) { rev[l] ^= 1; rev[r] ^= 1; rev[x] = 0; swap(c[x][0],c[x][1]); } if (tag[x]) { if (l) tag[l] += tag[x], mx[l] += tag[x], w[l] += tag[x]; if (r) tag[r] += tag[x], mx[r] += tag[x], w[r] += tag[x]; tag[x] = 0; } }