[关闭]
@ConanKID 2021-11-06T04:58:57.000000Z 字数 1661 阅读 131

主席树 学习笔记

学习笔记


为什么又要写学习笔记啊


前置知识:权值线段树。

首先利用权值线段树求集合的 Kth 是挺简单的一个事情。相当于一个二分的操作。

现在需要考虑的问题是区间静态 Kth,即给定一个序列 ,多次询问一个区间 中第 小的数。

还是考虑权值线段树上二分。设当前节点为 ,统计 中落在 的左儿子所代表的区间内的数的数量 。若 ,那么证明答案一定在左半区间里,直接向左儿子走。否则令 减去 ,然后向右儿子走。

问题转化为如何快速求出落在一个节点所代表区间内的数的数量。

考虑建出 棵线段树。对于 中的每一个 ,都单独建一棵权值线段树,存储把 中的每一个 插入线段树后的结果。同时对于每棵线段树,定义 表示落在节点 代表区间内的数的数量。这样求上面那个东西的时候就只需要把线段树 上的 与线段树 相减即可。

但是仍然有问题:这样做空间会爆炸。

继续分析发现,线段树 与线段树 相比,只多插入了一个数,至多只更改了 个节点( 表示值域)。因此只需要新建 个节点即可。

这里剽一张 OI-Wiki 上的图。

此处输入图片的描述

红色表示新建的节点。

这样每次插入操作最多新建 个节点,空间复杂度做到了

时间复杂度

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. const int maxn = 2e5 + 5;
  4. int n, m, len;
  5. int a[maxn], ind[maxn], rt[maxn];
  6. struct PresidentTree {
  7. #define mid (l + r >> 1)
  8. int ls[maxn << 5], rs[maxn << 5], sum[maxn << 5];
  9. int cnt;
  10. int build(int l, int r) {
  11. int k = ++cnt;
  12. if (l == r) return k;
  13. ls[k] = build(l, mid); rs[k] = build(mid + 1, r);
  14. return k;
  15. }
  16. int update(int k, int l, int r, int val) {
  17. int dir = ++cnt; ls[dir] = ls[k]; rs[dir] = rs[k]; sum[dir] = sum[k] + 1;
  18. if (l == r) return dir;
  19. if (val <= mid) ls[dir] = update(ls[k], l, mid, val);
  20. else rs[dir] = update(rs[k], mid + 1, r, val);
  21. return dir;
  22. }
  23. int query(int l, int r, int p, int q, int val) {
  24. if (l == r) return l;
  25. int x = sum[ls[q]] - sum[ls[p]];
  26. if (val <= x) return query(l, mid, ls[p], ls[q], val);
  27. else return query(mid + 1, r, rs[p], rs[q], val - x);
  28. }
  29. } t;
  30. void discrete() {
  31. memcpy(ind, a, sizeof(a));
  32. sort(ind + 1, ind + 1 + n);
  33. len = unique(ind + 1, ind + 1 + n) - ind - 1;
  34. for (int i = 1; i <= n; i++)
  35. a[i] = lower_bound(ind + 1, ind + 1 + len, a[i]) - ind;
  36. }
  37. int main() {
  38. cin >> n >> m;
  39. for (int i = 1; i <= n; i++)
  40. cin >> a[i];
  41. discrete();
  42. t.build(1, len);
  43. for (int i = 1; i <= n; i++)
  44. rt[i] = t.update(rt[i - 1], 1, len, a[i]);
  45. for (int i = 1; i <= m; i++) {
  46. int l, r, k; cin >> l >> r >> k;
  47. cout << ind[t.query(1, len, rt[l - 1], rt[r], k)] << endl;
  48. }
  49. return 0;
  50. }

这玩意儿就是主席树。一个妙妙的数据结构。


几道习题:


没有了。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注