前言
splay很早的时候就学过了,当时也挺理解的,不过可能是因为自己太菜了把,所以很长一段时间里,我都没有使用过splay,也忘了很多,以前学习的时候写的小结也是够水的,属于那种如果比赛的时候突然要用,我肯定是用不出来的那种,所以这次重新整理了一下splay,并且把相应功能做成了接口,加上了注释,再加上一些自己的理解,做成了一个类似stl的东西(当然为了速度和编程方便,我直接把所有的成员变量和函数都直接放在了程序里,没有用class来封装,内存也不是动态申请的,而是直接开了题目范围内的最大值),希望这样能对自己和其它有需要的人有点帮助。
对splay的理解(蜜汁自信
说到set,map,很多人都明白它们的特性和功能,set是一个点的集合,内部实现是一棵红黑树,map则是在set的基础上,把集合中的元素做成了映射中的键,同时每个键有自己对应的值,成了一个键值对的集合。在这里,我(不是很慎密地)把splay定义成一个更高功能的map,它不仅支持map(在acm竞赛中)常用的操作和(排序二叉树)性质外,还多了以下功能:
- 名次树,可以知道某个节点(或者某个key值)在splay中的名次,也可以知道splay中排名第几的节点
- 区间求和,查询key值在区间[l, r]的所有节点的value的某个值(和,最值)
- 区间修改,对key值在区间[l, r]的所有节点的value都加上某个值
- 区间插入/删除,用一个区间生成splay树,插入进原有的splay,或者把key值在区间[l, r]的节点集合从splay中删去
当然了如果需要区间翻转这个功能,那我们就不能把splay看作以key值为排序关键字的数据结构(但是前面说的一大段还是很实用的!),这时我们需要用一个数组记录相应的节点编号,然后又可以完成上述的4个操作外加区间翻转。
所以某种意义上splay就是一个可分裂与合并的序列。(精分现场QAQ
使用说明
下面的代码很大程度上只是给了一个如何使用功能的示例,比赛的时候理所应当地写更容易写(没有可读性)的版本。
因为splay的均摊复杂度达到O(logn)的特性,所以可以认为splay是一棵深度为logn的树。
splay中节点携带的信息只有在把这个节点移到根节点处才是真实的(因为存在lazy[]
数组),也只能修改根节点。
如果要对区间操作,那么自己定义了一个可操作区间的概念(自己define了一个op_seg),这时的区间操作和查询也是合法的,但是每次操作完后,要执行upSeg()
操作,使得区间被修改的信息传递到根节点
变量说明
- 结构类变量:
pa[]
,son[][]
,指明了一个节点的父节点和子节点 - 信息变量:
key[]
,value[]
,一个节点的键值对,决定了它在splay中的位置,显然splay中key
是不能改变的 - 统计变量:
siz[]
,sum[]
,这个节点代表的子树的统计信息 - lazy变量:
add[]
,rev[]
,缓存一个节点没有传递下去的操作信息
代码
#define MS(x, y) memset(x, y, sizeof(x))
#define lson(x) son[x][0]
#define rson(x) son[x][1]
#define opseg son[son[root][1]][0]
int n;
int pa[N], son[N][2], key[N], siz[N], tot, root;
LL value[N], sum[N], add[N];
bool rev[N];
// 更新一个节点的信息
inline void pushUp(int x) {
siz[x] = siz[lson(x)] + siz[rson(x)] + 1;
sum[x] = sum[lson(x)] + sum[rson(x)] + value[x];
}
// 把一个节点的lazy信息向下传递
inline void pushDown(int x) {
if (add[x]) {
add[lson(x)] += add[x]; value[lson(x)] += add[x]; sum[lson(x)] += add[x] * siz[lson(x)];
add[rson(x)] += add[x]; value[rson(x)] += add[x]; sum[rson(x)] += add[x] * siz[rson(x)];
add[x] = 0;
}
if (rev[x]) {
rev[lson(x)] ^= true; swap(son[lson(x)][0], son[lson(x)][1]);
rev[rson(x)] ^= true; swap(son[rson(x)][0], son[rson(x)][1]);
rev[x] = false;
}
}
// 每当可操作区的值有改变时,就要用一下这个函数进行更新
inline void upSeg() {
pushUp(rson(root));
pushUp(root);
}
// debug()的子函数
void treaval(int x) {
if (!x) return ;
pushDown(x);
treaval(lson(x));
printf("node: %2d: left: %2d right: %2d pa: %2d\n", x, lson(x), rson(x), pa[x]);
treaval(rson(x));
}
// 打印出整棵splay树供debug用
inline void debug() {
printf("root: %d\n", root);
treaval(root);
putchar(10);
}
// 生成一个key为pos,value为data,父节点为fa的节点
inline int newnode(int pos, int data, int fa) {
int ret;
ret = ++tot;
pa[ret] = fa; key[ret] = pos; value[ret] = data; siz[ret] = 1, sum[ret] = data;
add[ret] = 0; rev[ret] = false;
MS(son[ret], 0);
return ret;
}
// splay()的子函数
inline void Rotate(int x, int op) {
int y = pa[x];
pushDown(y); pushDown(x);
son[y][!op] = son[x][op];
pa[son[x][op]] = y;
if (pa[y]) son[pa[y]][rson(pa[y]) == y] = x;
pa[x] = pa[y];
son[x][op] = y;
pa[y] = x;
pushUp(y);
}
// 令x为fa的子节点,若fa == 0,则令x为根节点
inline void splay(int x, int fa) {
pushDown(x);
int y, op;
while (pa[x] != fa) {
if (pa[pa[x]] == fa) Rotate(x, lson(pa[x]) == x);
else {
y = pa[x];
op = (lson(pa[y]) == y);
if (son[y][op] == x) {
Rotate(x, !op);
Rotate(x, op);
} else {
Rotate(y, op);
Rotate(x, op);
}
}
}
pushUp(x);
if (fa == 0) root = x;
}
// 常规的二叉树插入函数,把pos,data这个键值对插入fa的子树中
// 该函数是insert()的子函数
int bstInsert(int pos, int data, int fa, int &rt) {
if (rt == 0) {
rt = newnode(pos, data, fa);
return rt;
}
pushDown(rt);
// assert(pos != key[rt])
int ret;
if (pos < key[rt]) ret = bstInsert(pos, data, rt, lson(rt));
else ret = bstInsert(pos, data, rt, rson(rt));
pushUp(rt);
return ret;
}
// 把某数组的[l, r]区间变成父节点是fa的一棵splay树
int build(int fa, int l, int r) {
if (l > r) return 0;
int mid = (l + r) >> 1;
int rt = newnode(mid, fa);
lson(rt) = build(rt, l, mid - 1);
rson(rt) = build(rt, mid + 1, r);
pushUp(rt);
return rt;
}
// 返回最后一个key值小于pos的节点编号
int getPrev(int pos, int rt) {
if (rt == 0) return 0;
pushDown(rt);
if (pos <= key[rt]) return getPrev(pos, lson(rt));
int ret = getPrev(pos, rson(rt));
if (ret) return ret;
return rt;
}
// 返回第一个key值大于pos的节点编号
int getNext(int pos, int rt) {
if (rt == 0) return 0;
pushDown(rt);
if (key[rt] <= pos) return getNext(pos, rson(rt));
int ret = getNext(pos, lson(rt));
if (ret) return ret;
return rt;
}
// 找到key值恰好为pos的节点编号,没有返回-1
inline int find(int pos) {
int rt = root;
while (rt) {
if (key[rt] < pos) rt = rson(rt);
if (pos < key[rt]) rt = lson(rt);
if (key[rt] == pos) return rt;
}
return -1;
}
// 删除根节点,这个是erase(x)的子函数
inline void deleteRoot() {
pushDown(root);
if (!lson(root) || !rson(root)) {
root = lson(root) + rson(root);
pa[root] = 0;
return ;
}
int k = getNext(key[root], root);
splay(k, root);
lson(k) = lson(root);
pa[lson(root)] = k;
root = k;
pa[root] = 0;
pushUp(root);
}
// 把key值在区间[l, r]的节点集合移到可操作区
inline void getSegment(int l, int r) {
l = getPrev(l, root);
r = getNext(r, root);
splay(l, 0);
splay(r, root);
}
// 把区间[l, r]翻转,这个操作违背了二叉排序树的性质
// 所以要用到这个操作的题不用key value对,而是直接把要操作的节点编号存下来
inline void Reverse(int l, int r) {
getSegment(l, r);
rev[opseg] ^= true;
swap(lson(opseg), rson(opseg));
}
// 删除key值在区间[l, r]的节点集合
inline void deleteSegment(int l, int r) {
getSegment(l, r);
opseg = 0;
upSeg();
}
// 对key值在区间[l, r]的所有节点都加上v
inline void update(int l, int r, int v) {
getSegment(l, r);
add[opseg] += v;
value[opseg] += v;
// sum[opseg] += 1LL * v * siz[opseg];
upSeg();
}
// 把key为pos,value为data的键值对加入splay中
inline void insert(int pos, int data) {
int ret = bstInsert(pos, data, 0, root);
splay(ret, 0);
}
// 把编号为x的节点从splay中移除
inline void erase(int x) {
splay(x, 0);
deleteRoot();
}
// 得到key值为pos的rank(是splay中的第几个节点)
inline int getRank(int pos) {
int x = find(pos);
if (x == -1) return -1;
splay(x, 0);
return siz[lson(x)] + 1;
}
// 得到key值排名第k大的节点编号
inline int getKth(int k) {
int x = root, s;
while (x) {
pushDown(x);
s = siz[lson(x)] + 1;
if (s == k) return x;
if (s > k) x = lson(x);
else k -= s, x = rson(x);
}
}
// 初始化
// 把节点0的所有信息都置为0
inline void init() {
tot = 0;
pa[0] = son[0][0] = son[0][1] = value[0] = key[0] = siz[0] = sum[0] = add[0] = rev[0] = 0;
// case # 1: splay开始为空,并且以后的操作中也会变空,那么我们加入一个key超级小的点和key超级大的点
root = newnode(-1, 0, 0);
rson(root) = newnode(INF, 0, root);
upSeg();
// case # 2: splay开始是一个序列,并且以后的操作中不会变空,那么我们把这个区间建成splay就好
root = build(0, 1, n);
}