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

前言

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的节点编号,没有返回-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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值