splay的不负责胡乱接口封装 写给自己看
来源:互联网 发布:2016淘宝客计入权重吗 编辑:程序博客网 时间:2024/05/16 23:55
前言
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的节点编号,没有返回-1inline 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]的所有节点都加上vinline 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的所有信息都置为0inline 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);}
- splay的不负责胡乱接口封装 写给自己看
- 写给自己看的
- 写给自己看的文章
- 无题!写给自己看的!
- 乱七八糟的东西写给自己看的
- 刷k短路模板题【不无聊就别点了,我写给自己看的】
- 写给我自己看的文章
- 写给我自己看的文章1
- 写给自己看的循环队列
- 字符串总结--写给自己看的
- 继承qstringlist ,写给自己看的
- 机器学习(写给自己看的)
- 写给自己看的五子棋人机思路
- 写给自己看的OpenCV配置指南 :-)
- 我写给自己的你们不要看
- 写给自己看的博客_DoTween
- 一段简短的说明,写给自己看
- 写给自己看的SSH整合
- Mustache.js 模板的使用
- Spring、SpringMVC、MyBatis整合
- JSON和java对象的互转
- Kendo UI 使用小知识点汇总
- java分布式定时器实现(rabbitmq版)
- splay的不负责胡乱接口封装 写给自己看
- php获取指定的时间戳
- python 中 self的相关总结
- 面试题–总结知识点
- Linux进阶之 tail 命令
- [Chrome] Google Chrome 浏览器快捷键
- Python使用spark时出現版本不同的错误
- socket通讯工具类
- ORACLE的char、nchar、varchar、varchar2、nvarchar2的区别