【ACWing】2476. 树套树

题目地址:

https://www.acwing.com/problem/content/2478/

请你写出一种数据结构,来维护一个长度为 n n n的数列,其中需要提供以下操作:
1 l r x,查询整数 x x x在区间 [ l , r ] [l,r] [l,r]内的排名(如果 x x x出现多于 1 1 1次,则求的是最小排名)。
2 l r k,查询区间 [ l , r ] [l,r] [l,r]内排名为 k k k的值。
3 pos x,将pos位置的数修改为 x x x
4 l r x,查询整数 x x x在区间 [ l , r ] [l,r] [l,r]内的前驱(前驱定义为小于 x x x,且最大的数)。
5 l r x,查询整数 x x x在区间 [ l , r ] [l,r] [l,r]内的后继(后继定义为大于 x x x,且最小的数)。
数列中的位置从左到右依次标号为 1 ∼ n 1∼n 1n
区间 [ l , r ] [l,r] [l,r]表示从位置 l l l到位置 r r r之间(包括两端点)的所有数字。
区间内排名为 k k k的值指区间内从小到大排在第 k k k位的数值。(位次从 1 1 1开始)

输入格式:
第一行包含两个整数 n , m n,m n,m,表示数列长度以及操作次数。
第二行包含 n n n个整数,表示数列。
接下来 m m m行,每行包含一个操作指令,格式如题目所述。

输出格式:
对于所有操作1,2,4,5,每个操作输出一个查询结果,每个结果占一行。

数据范围:
1 ≤ n , m ≤ 5 × 1 0 4 1≤n,m≤5×10^4 1n,m5×104,
1 ≤ l ≤ r ≤ n 1≤l≤r≤n 1lrn,
1 ≤ p o s ≤ n 1≤pos≤n 1posn,
1 ≤ k ≤ r − l + 1 1≤k≤r−l+1 1krl+1,
0 ≤ x ≤ 1 0 8 0≤x≤10^8 0x108,
有序数列中的数字始终满足在 [ 0 , 1 0 8 ] [0,10^8] [0,108]范围内,
数据保证所有操作一定合法,所有查询一定有解。

思路是树套树,外层是线段树维护区间信息,每个线段树节点内部存一个FHQ Treap,维护该区间的有序数列的信息。几个操作做法分别如下:
1、求 x x x [ l , r ] [l,r] [l,r]内的排名可以转化为求 [ l , r ] [l,r] [l,r]中比 x x x小的数的个数(个数加 1 1 1即为排名)。用线段树可以将 [ l , r ] [l,r] [l,r]分解为不相交的对数级别的区间,每个区间查询一下FHQ Treap里小于 x x x的个数,将个数加总即可。
2、求 [ l , r ] [l,r] [l,r]内排名为 k k k的值,直接求比较困难,可以用二分。这里即求排名小于等于 k k k的最大数,而排名可以用上面的1来做。由于数据范围是 [ 0 , 1 0 8 ] [0,10^8] [0,108],可以在这个区间二分。
3、将位置 p p p的数修改为 x x x,我们事先是已经将数列读入到一个数组里的,查询数组即知位置 p p p的数是什么,接着我们在线段树里将包含位置 p p p的区间都做一下更新即可,更新的方式是在FHQ Treap里将旧的数删掉,然后插入 x x x
4、求 x x x [ l , r ] [l,r] [l,r]里的前驱,即求 [ l , r ] [l,r] [l,r]里小于 x x x的最大数。线段树可以将 [ l , r ] [l,r] [l,r]分解为不相交的对数级别的区间,每个区间查询一下FHQ Treap里小于 x x x的最大数,然后在这些最大数里求最大的即可。
5、求 x x x [ l , r ] [l,r] [l,r]里的后继,与 4 4 4类似。

有几个需要注意的地方:
1、FHQ Treap节点要开 n log ⁡ n + m n\log n+m nlogn+m个,线段树有 log ⁡ n \log n logn层,每层的FHQ Treap节点要 n n n个,所以线段树需要的总的FHQ Treap的节点数为 n log ⁡ n n\log n nlogn;此外对于操作 3 3 3,每次操作一下都会新开一个节点,所以总共就是 n log ⁡ n + m n\log n+m nlogn+m个;
2、由于这里FHQ Treap维护的是有序数列,在建树的时候要将节点逐个插入,以保证有序性;
3、由于查前驱和后继的时候,可能出现某个区间的FHQ Treap并不存在某个数的前驱,方便起见,如果不存在前驱,则设前驱为负无穷;如果不存在后继,则设后继为正无穷。

代码如下:

#include <iostream>
using namespace std;

const int N = 1e5 + 10, INF = 1e9;
int n, m;
int a[N];

namespace FHQ {

struct Node {
#define lc(u) tr[u].l
#define rc(u) tr[u].r
#define val(u) tr[u].val
#define sz(u) tr[u].sz
  int l, r;
  int val, sz;
  int rnd;
} tr[N * 21];
int idx;
int get_node(int v) {
  tr[++idx] = {0, 0, v, 1, rand()};
  return idx;
}

void pushup(int u) { sz(u) = sz(lc(u)) + sz(rc(u)) + 1; }

void split(int u, int val, int &x, int &y) {
  if (!u) x = y = 0;
  else {
    if (val < val(u)) {
      y = u;
      split(lc(u), val, x, lc(u));
      pushup(y);
    } else {
      x = u;
      split(rc(u), val, rc(u), y);
      pushup(x);
    }
  }
}

int merge(int x, int y) {
  if (!x || !y) return x | y;
  else {
    if (tr[x].rnd > tr[y].rnd) {
      rc(x) = merge(rc(x), y);
      pushup(x);
      return x;
    } else {
      lc(y) = merge(x, lc(y));
      pushup(y);
      return y;
    }
  }
}

// 针对每个具体的FHQ Treap的函数要写在struct内部
struct FHQ {
  int root;

  void insert(int v) {
    int x, y, z;
    split(root, v - 1, x, z);
    y = get_node(v);
    root = merge(merge(x, y), z);
  }
  // 将Treap中值为w的节点删掉,再插入v
  void modify(int w, int v) {
    int x, y, z;
    split(root, w - 1, x, z);
    split(z, w, y, z);
    y = merge(lc(y), rc(y));
    root = merge(merge(x, y), z);
	insert(v);
  }

  // 求Treap中小于v的节点个数
  int get_less(int v) {
    int x, y;
    split(root, v - 1, x, y);
    int res = sz(x);
    root = merge(x, y);
    return res;
  }

  // 求Treap中v的前驱。如果不存在则返回负无穷
  int get_prev(int v) {
    int u = root, res = -INF;
    while (u) {
      if (v > val(u)) res = max(res, val(u)), u = rc(u);
      else u = lc(u);
    }
    return res;
  }

  // 求Treap中v的后继。如果不存在则返回正无穷
  int get_next(int v) {
    int u = root, res = INF;
    while (u) {
      if (v < val(u)) res = min(res, val(u)), u = lc(u);
      else u = rc(u);
    }
    return res;
  }
};

}  // namespace FHQ

namespace SegTree {

// 每个线段树节点里有一个FHQ Treap
struct Node {
  int l, r;
  FHQ::FHQ fhq;
} tr[N << 2];

void build(int u, int l, int r) {
  tr[u] = {l, r};
  // 将该区间的值插入该区间的那个FHQ Treap
  for (int i = l; i <= r; i++) tr[u].fhq.insert(a[i]);
  if (l == r) return;

  int mid = l + r >> 1;
  build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}

int get_less(int u, int l, int r, int x) {
  if (l <= tr[u].l && tr[u].r <= r) return tr[u].fhq.get_less(x);
  int mid = tr[u].l + tr[u].r >> 1;
  int res = 0;
  if (l <= mid) res += get_less(u << 1, l, r, x);
  if (r > mid) res += get_less(u << 1 | 1, l, r, x);
  return res;
}

int get_key_from_rank(int lq, int rq, int rk) {
  int l = 0, r = 1e8;
  while (l < r) {
    int mid = l + r + 1 >> 1;
    if (get_less(1, lq, rq, mid) + 1 <= rk) l = mid;
    else r = mid - 1;
  }

  return l;
}

void modify(int u, int pos, int x) {
  if (pos < tr[u].l || pos > tr[u].r) return;
  tr[u].fhq.modify(a[pos], x);
  int mid = tr[u].l + tr[u].r >> 1;
  if (pos <= mid) modify(u << 1, pos, x);
  else modify(u << 1 | 1, pos, x);
}

int get_prev(int u, int l, int r, int x) {
  if (l <= tr[u].l && tr[u].r <= r) return tr[u].fhq.get_prev(x);
  int mid = tr[u].l + tr[u].r >> 1;
  int res = -INF;
  if (l <= mid) res = max(res, get_prev(u << 1, l, r, x));
  if (r > mid) res = max(res, get_prev(u << 1 | 1, l, r, x));
  return res;
}

int get_next(int u, int l, int r, int x) {
  if (l <= tr[u].l && tr[u].r <= r) return tr[u].fhq.get_next(x);
  int mid = tr[u].l + tr[u].r >> 1;
  int res = INF;
  if (l <= mid) res = min(res, get_next(u << 1, l, r, x));
  if (r > mid) res = min(res, get_next(u << 1 | 1, l, r, x));
  return res;
}

}  // namespace SegTree

int main() {
  scanf("%d%d", &n, &m);
  for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
  SegTree::build(1, 1, n);
  while (m--) {
    int op, l, r, pos, x;
    scanf("%d", &op);
    if (op == 1 || op == 2 || op == 4 || op == 5) scanf("%d%d%d", &l, &r, &x);
    else scanf("%d%d", &pos, &x);

    if (op == 1) printf("%d\n", SegTree::get_less(1, l, r, x) + 1);
    else if (op == 2) printf("%d\n", SegTree::get_key_from_rank(l, r, x));
    else if (op == 3) {
      SegTree::modify(1, pos, x);
      // 线段树更新完,也要把数组a更新一下,以便于下次的修改
      a[pos] = x;
    } else if (op == 4) printf("%d\n", SegTree::get_prev(1, l, r, x));
    else printf("%d\n", SegTree::get_next(1, l, r, x));
  }
}

操作1、3、4、5时间复杂度 O ( log ⁡ 2 n ) O(\log ^2n) O(log2n),操作2时间 O ( log ⁡ R log ⁡ 2 n ) O(\log R\log^2n) O(logRlog2n) R R R是数据范围,空间 O ( n log ⁡ n ) O(n\log n) O(nlogn)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值