splay的不负责胡乱接口封装 写给自己看

来源:互联网 发布:2016淘宝客计入权重吗 编辑:程序博客网 时间:2024/05/16 23:55

前言

splay很早的时候就学过了,当时也挺理解的,不过可能是因为自己太菜了把,所以很长一段时间里,我都没有使用过splay,也忘了很多,以前学习的时候写的小结也是够水的,属于那种如果比赛的时候突然要用,我肯定是用不出来的那种,所以这次重新整理了一下splay,并且把相应功能做成了接口,加上了注释,再加上一些自己的理解,做成了一个类似stl的东西(当然为了速度和编程方便,我直接把所有的成员变量和函数都直接放在了程序里,没有用class来封装,内存也不是动态申请的,而是直接开了题目范围内的最大值),希望这样能对自己和其它有需要的人有点帮助。

对splay的理解(蜜汁自信

说到set,map,很多人都明白它们的特性和功能,set是一个点的集合,内部实现是一棵红黑树,map则是在set的基础上,把集合中的元素做成了映射中的键,同时每个键有自己对应的值,成了一个键值对的集合。在这里,我(不是很慎密地)把splay定义成一个更高功能的map,它不仅支持map(在acm竞赛中)常用的操作和(排序二叉树)性质外,还多了以下功能:

  1. 名次树,可以知道某个节点(或者某个key值)在splay中的名次,也可以知道splay中排名第几的节点
  2. 区间求和,查询key值在区间[l, r]的所有节点的value的某个值(和,最值)
  3. 区间修改,对key值在区间[l, r]的所有节点的value都加上某个值
  4. 区间插入/删除,用一个区间生成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);}
原创粉丝点击