[关闭]
@dxbdly 2021-07-11T15:18:27.000000Z 字数 16972 阅读 333

FHQ-Treap

OI-算法


图片引自博客 (怪我美术太差画不出,找到几个操作是有图的,勉强看看吧)

本文作者 ,转载或引用请加上原文链接

修润了部分 ,更新了部分代码(虽然注释没了,但应该不打紧),加入了新的例题

前言

我们在学习 时,会发现其实 的复杂度并不稳定

如果我们构造一个单调序列,那么树的形状会变成一条链,单次操作时间复杂度

那么这时候我们需要一些方法使得 平衡

众所周知,平衡树大多带旋转操作,并且码量巨大无比(反正我没写出来过)

今天介绍一种不需要旋转,且 非常好写,非常好写,非常好写 的平衡树 ——

的思想

之前,我们先来看看

,即 + 堆

他的思想是:对于 树上的每个节点,我们都 随机 给他一个权值 ,我们需要保证 满足堆的性质(大根,小根均可)

则他的期望形状是平衡的。(是不是很玄学,至于怎么证吗……请读者自证)

的思想与操作

维护的操作是通过不断旋转,码量巨大而且常数**

用的则是合并 与分裂 的操作。

对于一个节点,我们需要记录以下变量:

  1. //The code is from dxbdly
  2. struct node {
  3. int lc, rc;
  4. int siz, rad, val;
  5. }Tree[maxn << 1];

我们直接来看操作:

操作

动态开点,随机获取 值。

  1. //The code is from dxbdly
  2. inline int Add(int val) {
  3. tot++;
  4. Tree[tot].siz = 1, Tree[tot].lc = Tree[tot].rc = 0, Tree[tot].val = val, Tree[tot].rad = rand();
  5. return tot;
  6. }

操作

对于一节点由于其左儿子,右儿子有变化,需要重新统计

  1. //The code is from dxbdly
  2. inline void Update(int k) {
  3. Tree[k].siz = Tree[Tree[k].lc].siz + Tree[Tree[k].rc].siz + 1;
  4. }

操作

操作主要维护堆的性质,将两个 合成一个,具体可以参考左偏树的合并。


  1. //The code is from dxbdly
  2. inline void Merge(int &k, int a, int b) {
  3. if(!a || !b) { k = a + b; return ; }
  4. if(Tree[a].rad <= Tree[b].rad) k = a, Merge(Tree[k].rc, Tree[k].rc, b);
  5. else k = b, Merge(Tree[k].lc, a, Tree[k].lc);
  6. Update(k);
  7. }

操作

操作主要满足 性质,有两种写法:

按权值分裂:

则若分裂标准为 ,我们要使得分裂出的左树点权值均 ,右树均

分三种情况讨论:

设 当前子树根节点为 ,要分裂出的两棵子树分别为 ,分裂标准为


性质可知,左子树所有点均
左子树确定 我们只需要在右子树递归求 的右子树, 即可


同理
我们只需要在左子树递归处理 的左子树。

代码:

  1. //The code is from dxbdly
  2. inline void Split(int k, int &a, int &b, int val) {
  3. if(!k) { a = b = 0; return ; }
  4. if(Tree[k].val <= val) a = k, Split(Tree[k].rc, Tree[k].rc, b, val);
  5. else b = k, Split(Tree[k].lc, a, Tree[k].lc, val);
  6. Update(k);
  7. }

根据 分裂:

标准量为: ,则左树 ,右树均

差不多的讨论方式,可以自己试着推一下。

代码:

  1. //The code is from dxbdly
  2. inline void Split(int k, int &a, int &b, int x) {
  3. if(!k) { a = b = 0; return ; }
  4. if(Tree[Tree[k].lc].siz + 1 <= x) a = k, Split(Tree[k].rc, Tree[k].rc, b, x - Tree[Tree[k].lc].siz - 1);
  5. else b = k, Split(Tree[k].lc, a, Tree[k].lc, x);
  6. Update(k);
  7. }

操作

若插入的值为

则以 为标准量 整棵树,

然后

最后重新全部

代码:

  1. //The code is from dxbdly
  2. inline void Insert(int &k, int val) {
  3. int a, b, c = Add(val);
  4. Split(k, a, b, val), Merge(a, a, c), Merge(k, a, b);
  5. }

操作

若删除的点权值为

为标准量将整棵树

再以 为标准量将 ,这样就可以分离出权值为 的点,它为 的根节点

的左右儿子 ,就可以将其删除

然后全部重新

代码:

  1. //The code is from dxbdly
  2. inline void Delte(int &k, int val) {
  3. int a, b, c;
  4. Split(k, a, b, val), Split(a, a, c, val - 1);
  5. Merge(c, Tree[c].lc, Tree[c].rc), Merge(a, a, c), Merge(k, a, b);
  6. }

操作

我们若想知道排名为 的数。

与主席树求第 小相似。

如果左子树 则就是当前节点。

如果左子树 不变,递归左子树找

如果左子树 变为 ,递归右子树找。

代码:

  1. //The code is from dxbdly
  2. inline int Find_Val(int k, int x) {
  3. while(Tree[Tree[k].lc].siz + 1 != x) {
  4. if(Tree[Tree[k].lc].siz >= x) k = Tree[k].lc;
  5. else x -= (Tree[Tree[k].lc].siz + 1), k = Tree[k].rc;
  6. }
  7. return Tree[k].val;
  8. }

操作

我们若想知道权值为 的数的排名。

只要将按 为标准量 ,此时左树节点均 ,所以左树的 就是答案。

代码:

  1. //The code is from dxbdly
  2. inline int Find_Rnk(int &k, int val) {
  3. int a, b, ans;
  4. Split(k, a, b, val - 1), ans = Tree[a].siz + 1, Merge(k, a, b);
  5. return ans;
  6. }

操作

我们若想知道权值为 的前驱。

只要按 为标准量 ,此时左树的最大节点(排名第 )便是前驱。

只需在左树中 左树的第 即是答案。

代码:

  1. //The code is from dxbdly
  2. inline int Find_Pre(int &k, int val) {
  3. int a, b, ans;
  4. Split(k, a, b, val - 1), ans = Find_Val(a, Tree[a].siz), Merge(k, a, b);
  5. return ans;
  6. }

操作

我们若想知道权值为 的后继。

只要以 为标准量 ,此时右树的最小节点(排名为 )

只要在右树中 右树的第 即使答案

代码:

  1. //The code is from dxbdly
  2. inline int Find_Nex(int &k, int val) {
  3. int a, b, ans;
  4. Split(k, a, b, val), ans = Find_Val(b, 1), Merge(k, a, b);
  5. return ans;
  6. }

常数优化 ——

由于我们的 需要一个随机权值,而随机函数的常数是极大的。

这使得平衡树上点数过多的时候程序会变慢许多,这里我们可以考虑手写一个

实现方式很简单,取一个大质数 ,让一个初始值为 每次乘上 ,自然溢出即可。

  1. const int maxn = 1e5 + 5, base = 13331;
  2. int n, tot, root, res = 1;
  3. inline int Rand() {
  4. res *= base; return res;
  5. }

好了,平衡树的基本操作我们就讲完了。

时间复杂度

AC总代码:

  1. //The code is from dxbdly
  2. #include<bits/stdc++.h>
  3. using namespace std;
  4. inline int read() {
  5. int x = 0;
  6. char c = getchar();
  7. bool f = 0;
  8. while(!isdigit(c)) f |= (c == '-'), c = getchar();
  9. while(isdigit(c)) x = (x * 10) + (c ^ 48), c = getchar();
  10. return f ? -x : x;
  11. }
  12. const int maxn = 1e5 + 5, base = 13331;
  13. int n, tot, root, res = 1;
  14. struct node {
  15. int lc, rc;
  16. int siz, rad, val;
  17. }Tree[maxn << 1];
  18. inline int Rand() {
  19. res *= base; return res;
  20. }
  21. inline int Add(int val) {
  22. tot++;
  23. Tree[tot].siz = 1, Tree[tot].lc = Tree[tot].rc = 0, Tree[tot].val = val, Tree[tot].rad = Rand();
  24. return tot;
  25. }
  26. inline void Update(int k) {
  27. Tree[k].siz = Tree[Tree[k].lc].siz + Tree[Tree[k].rc].siz + 1;
  28. }
  29. inline void Split(int k, int &a, int &b, int val) {
  30. if(!k) { a = b = 0; return ; }
  31. if(Tree[k].val <= val) a = k, Split(Tree[k].rc, Tree[k].rc, b, val);
  32. else b = k, Split(Tree[k].lc, a, Tree[k].lc, val);
  33. Update(k);
  34. }
  35. inline void Merge(int &k, int a, int b) {
  36. if(!a || !b) { k = a + b; return ; }
  37. if(Tree[a].rad <= Tree[b].rad) k = a, Merge(Tree[k].rc, Tree[k].rc, b);
  38. else k = b, Merge(Tree[k].lc, a, Tree[k].lc);
  39. Update(k);
  40. }
  41. inline void Insert(int &k, int val) {
  42. int a, b, c = Add(val);
  43. Split(k, a, b, val), Merge(a, a, c), Merge(k, a, b);
  44. }
  45. inline void Delte(int &k, int val) {
  46. int a, b, c;
  47. Split(k, a, b, val), Split(a, a, c, val - 1);
  48. Merge(c, Tree[c].lc, Tree[c].rc), Merge(a, a, c), Merge(k, a, b);
  49. }
  50. inline int Find_Val(int k, int x) {
  51. while(Tree[Tree[k].lc].siz + 1 != x) {
  52. if(Tree[Tree[k].lc].siz >= x) k = Tree[k].lc;
  53. else x -= (Tree[Tree[k].lc].siz + 1), k = Tree[k].rc;
  54. }
  55. return Tree[k].val;
  56. }
  57. inline int Find_Rnk(int &k, int val) {
  58. int a, b, ans;
  59. Split(k, a, b, val - 1), ans = Tree[a].siz + 1, Merge(k, a, b);
  60. return ans;
  61. }
  62. inline int Find_Pre(int &k, int val) {
  63. int a, b, ans;
  64. Split(k, a, b, val - 1), ans = Find_Val(a, Tree[a].siz), Merge(k, a, b);
  65. return ans;
  66. }
  67. inline int Find_Nex(int &k, int val) {
  68. int a, b, ans;
  69. Split(k, a, b, val), ans = Find_Val(b, 1), Merge(k, a, b);
  70. return ans;
  71. }
  72. int main() {
  73. n = read();
  74. for(register int i = 1; i <= n; ++i) {
  75. int op = read(), x = read();
  76. if(op == 1) Insert(root, x);
  77. if(op == 2) Delte(root, x);
  78. if(op == 3) printf("%d\n", Find_Rnk(root, x));
  79. if(op == 4) printf("%d\n", Find_Val(root, x));
  80. if(op == 5) printf("%d\n", Find_Pre(root, x));
  81. if(op == 6) printf("%d\n", Find_Nex(root, x));
  82. }
  83. return 0;
  84. }

的区间翻转

众所周知, 是支持区间翻转的,其实 也行。

题面

题解:

首先我们考虑如何取出 的区间,考虑以 为标准进行 操作

出 区间 再将区间 出去,就得到了区间

再考虑如何实现翻转

我们借用线段树 的思想,如果当前区间被翻转则打上标记

每次做 操作时将标记下传

每次下传时,将左右儿子的 ,再将左右儿子交换,最后将自己的 清空。

注意: 操作是将要参与合并的子树下传

  1. //The code is from dxbdly
  2. inline void Push_Down(int k) {
  3. if(Tree[k].tag) {
  4. Tree[Tree[k].lc].tag ^= 1, Tree[Tree[k].rc].tag ^= 1;
  5. Tree[k].tag = 0, swap(Tree[k].lc, Tree[k].rc);
  6. }
  7. }

最后中序遍历一遍整棵树,将剩余的标记全部下传,输出即可。

时间复杂度

AC代码:

  1. //The code is from dxbdly
  2. #include<bits/stdc++.h>
  3. using namespace std;
  4. inline int read() {
  5. int x = 0;
  6. char c = getchar();
  7. bool f = 0;
  8. while(!isdigit(c)) f |= (c == '-'), c = getchar();
  9. while(isdigit(c)) x = (x * 10) + (c ^ 48), c = getchar();
  10. return f ? -x : x;
  11. }
  12. const int maxn = 1e6 + 5, base = 13331;
  13. int n, m, tot, root, last, res = 1;
  14. struct node {
  15. int lc, rc;
  16. int siz, rad, val, tag;
  17. }Tree[maxn << 1];
  18. inline int Rand() {
  19. res *= base; return res;
  20. }
  21. inline int Add(int val) {
  22. tot++;
  23. Tree[tot].siz = 1, Tree[tot].lc = Tree[tot].rc = 0, Tree[tot].val = val, Tree[tot].rad = Rand();
  24. return tot;
  25. }
  26. inline void Update(int k) {
  27. Tree[k].siz = Tree[Tree[k].lc].siz + Tree[Tree[k].rc].siz + 1;
  28. }
  29. inline void Push_Down(int k) {
  30. if(Tree[k].tag) {
  31. Tree[Tree[k].lc].tag ^= 1, Tree[Tree[k].rc].tag ^= 1;
  32. Tree[k].tag = 0, swap(Tree[k].lc, Tree[k].rc);
  33. }
  34. }
  35. inline void Split(int k, int &a, int &b, int x) {
  36. if(!k) { a = b = 0; return ; }
  37. Push_Down(k);
  38. if(Tree[Tree[k].lc].siz + 1 <= x) a = k, Split(Tree[k].rc, Tree[k].rc, b, x - Tree[Tree[k].lc].siz - 1);
  39. else b = k, Split(Tree[k].lc, a, Tree[k].lc, x);
  40. Update(k);
  41. }
  42. inline void Merge(int &k, int a, int b) {
  43. if(!a || !b) { k = a + b; return ; }
  44. if(Tree[a].rad <= Tree[b].rad) k = a, Push_Down(k), Merge(Tree[k].rc, Tree[k].rc, b);
  45. else k = b, Push_Down(k), Merge(Tree[k].lc, a, Tree[k].lc);
  46. Update(k);
  47. }
  48. inline void Insert(int &k, int val) {
  49. int a, b, c = Add(val);
  50. Split(k, a, b, val), Merge(a, a, c), Merge(k, a, b);
  51. }
  52. inline void Change(int &k, int l, int r) {
  53. int a, b, c;
  54. Split(k, b, c, r), Split(b, a, b, l - 1), Tree[b].tag ^= 1, Merge(b, b, c), Merge(k, a, b);
  55. }
  56. inline void Get_Ans(int k) {
  57. if(!k) return ;
  58. Push_Down(k);
  59. Get_Ans(Tree[k].lc);
  60. printf("%d ", Tree[k].val);
  61. Get_Ans(Tree[k].rc);
  62. }
  63. int main() {
  64. n = read(), m = read();
  65. for(register int i = 1; i <= n; ++i) Insert(root, i);
  66. for(register int i = 1; i <= m; ++i) {
  67. int l = read(), r = read();
  68. Change(root, l, r);
  69. }
  70. Get_Ans(root);
  71. return 0;
  72. }

例题

HNOI2002 营业额统计

题面

分析

模板题,以此将营业额插入平衡树,求出前继和后继,取最小即可

细节:

由于有的点没有前继或后继,在最开始的时候插入 防止查询越界

由于营业额可能相等,可能会导致前继和后继都是自己,所以找前继的时候可以找 营业额 的前继

代码:

  1. //The code is from dxbdly
  2. #include<bits/stdc++.h>
  3. using namespace std;
  4. inline int read() {
  5. int x = 0;
  6. char c = getchar();
  7. bool f = 0;
  8. while(!isdigit(c)) f |= (c == '-'), c = getchar();
  9. while(isdigit(c)) x = (x * 10) + (c ^ 48), c = getchar();
  10. return f ? -x : x;
  11. }
  12. const int maxn = 1e6 + 5, base = 13331, INF = 1e9 + 7;
  13. int n, tot, root, res = 1;
  14. struct node {
  15. int lc, rc;
  16. int siz, rad, val;
  17. }Tree[maxn << 1];
  18. inline int Rand() {
  19. res *= base; return res;
  20. }
  21. inline int Add(int val) {
  22. tot++;
  23. Tree[tot].siz = 1, Tree[tot].lc = Tree[tot].rc = 0, Tree[tot].val = val, Tree[tot].rad = Rand();
  24. return tot;
  25. }
  26. inline void Update(int k) {
  27. Tree[k].siz = Tree[Tree[k].lc].siz + Tree[Tree[k].rc].siz + 1;
  28. }
  29. inline void Split(int k, int &a, int &b, int val) {
  30. if(!k) { a = b = 0; return ; }
  31. if(Tree[k].val <= val) a = k, Split(Tree[k].rc, Tree[k].rc, b, val);
  32. else b = k, Split(Tree[k].lc, a, Tree[k].lc, val);
  33. Update(k);
  34. }
  35. inline void Merge(int &k, int a, int b) {
  36. if(!a || !b) { k = a + b; return ; }
  37. if(Tree[a].rad <= Tree[b].rad) k = a, Merge(Tree[k].rc, Tree[k].rc, b);
  38. else k = b, Merge(Tree[k].lc, a, Tree[k].lc);
  39. Update(k);
  40. }
  41. inline void Insert(int &k, int val) {
  42. int a, b, c = Add(val);
  43. Split(k, a, b, val), Merge(a, a, c), Merge(k, a, b);
  44. }
  45. inline int Find_Val(int k, int x) {
  46. while(Tree[Tree[k].lc].siz + 1 != x) {
  47. if(Tree[Tree[k].lc].siz >= x) k = Tree[k].lc;
  48. else x -= (Tree[Tree[k].lc].siz + 1), k = Tree[k].rc;
  49. }
  50. return Tree[k].val;
  51. }
  52. inline int Find_Pre(int &k, int val) {
  53. int a, b, ans;
  54. Split(k, a, b, val - 1), ans = Find_Val(a, Tree[a].siz), Merge(k, a, b);
  55. return ans;
  56. }
  57. inline int Find_Nex(int &k, int val) {
  58. int a, b, ans;
  59. Split(k, a, b, val), ans = Find_Val(b, 1), Merge(k, a, b);
  60. return ans;
  61. }
  62. int main() {
  63. n = read();
  64. Insert(root, -INF), Insert(root, INF);
  65. int ans = 0;
  66. for(register int i = 1; i <= n; ++i) {
  67. int x = read();
  68. int pre = Find_Pre(root, x + 1), nex = Find_Nex(root, x);
  69. ans += (i != 1 ? min(x - pre, nex - x) : x);
  70. Insert(root, x);
  71. }
  72. printf("%d", ans);
  73. return 0;
  74. }

思考与总结

由于 并不是用旋转实现,在使用时更应当防止越界的情况出现

在一些题目的解决中,提前加入一些边界点,会使得后续的处理更加方便。

TJOI2007 书架

题面

分析

先将初始的 本书放进平衡树,权值为编号,考虑新加进来的一本书。

发现如果按 划分,就不需要考虑之前新加入的书对于位置的影响了,直接 即可

拿个映射记一下编号对应的名字, 即可。

代码:

  1. //The code is from dxbdly
  2. #include<bits/stdc++.h>
  3. using namespace std;
  4. inline int read() {
  5. int x = 0;
  6. char c = getchar();
  7. bool f = 0;
  8. while(!isdigit(c)) f |= (c == '-'), c = getchar();
  9. while(isdigit(c)) x = (x * 10) + (c ^ 48), c = getchar();
  10. return f ? -x : x;
  11. }
  12. const int maxn = 1e5 + 5, base = 13331;
  13. int n, m, q, tot, root, res = 1;
  14. struct node {
  15. int lc, rc;
  16. int siz, rad, val, tag;
  17. }Tree[maxn << 1];
  18. char s[maxn << 1][15];
  19. inline int Rand() {
  20. res *= base; return res;
  21. }
  22. inline int Add(int val) {
  23. tot++;
  24. Tree[tot].siz = 1, Tree[tot].lc = Tree[tot].rc = 0, Tree[tot].val = val, Tree[tot].rad = Rand();
  25. return tot;
  26. }
  27. inline void Update(int k) {
  28. Tree[k].siz = Tree[Tree[k].lc].siz + Tree[Tree[k].rc].siz + 1;
  29. }
  30. inline void Split(int k, int &a, int &b, int x) {
  31. if(!k) { a = b = 0; return ; }
  32. if(Tree[Tree[k].lc].siz + 1 <= x) a = k, Split(Tree[k].rc, Tree[k].rc, b, x - Tree[Tree[k].lc].siz - 1);
  33. else b = k, Split(Tree[k].lc, a, Tree[k].lc, x);
  34. Update(k);
  35. }
  36. inline void Merge(int &k, int a, int b) {
  37. if(!a || !b) { k = a + b; return ; }
  38. if(Tree[a].rad <= Tree[b].rad) k = a, Merge(Tree[k].rc, Tree[k].rc, b);
  39. else k = b, Merge(Tree[k].lc, a, Tree[k].lc);
  40. Update(k);
  41. }
  42. inline void Insert(int &k, int val, int x) {
  43. int a, b, c = Add(val);
  44. Split(k, a, b, x - 1), Merge(a, a, c), Merge(k, a, b);
  45. }
  46. inline int Find_Val(int k, int x) {
  47. while(Tree[Tree[k].lc].siz + 1 != x) {
  48. if(Tree[Tree[k].lc].siz >= x) k = Tree[k].lc;
  49. else x -= (Tree[Tree[k].lc].siz + 1), k = Tree[k].rc;
  50. }
  51. return Tree[k].val;
  52. }
  53. int main() {
  54. n = read();
  55. for(register int i = 1; i <= n; ++i) { Insert(root, i, i), scanf("%s", s[i]); }
  56. m = read();
  57. for(register int i = 1, x; i <= m; ++i) { scanf("%s", s[n + i]), x = read() + 1, Insert(root, n + i, x); }
  58. q = read();
  59. while(q--) {
  60. int x = read() + 1;
  61. printf("%s\n", s[Find_Val(root, x)]);
  62. }
  63. return 0;
  64. }

思考与总结

由于 有独特的 操作

所以我们做题时可以考虑以不同参数为划分标准的效果,说不定会有意想不到的收获

HNOI2012 永无乡

题面

分析

看到询问操作,容易想到使用平衡树来解决

考虑修改操作,如果将一个连通块看成一颗平衡树,我们相当于就是要做平衡树合并

考虑暴力将其中一颗平衡树的每一个点拆下来, 到另外一棵树中去

这样的复杂度最高是 的,不可取

由于复杂度上限在合并操作上,可以考虑 (启发式合并)

我们考虑合并时,将 小的平衡树合并到 大的树上。

再来分析一下时间复杂度:

由于每次合并都是将小的插入大的,所以 至少会变成 小 树的

所以最多被合并 次,时间复杂度 可以通过

细节:

合并之后新的根是不确定的,所以要建一个并查集维护每个点所在树的根,方便查询。

拆点下来的时候,要将点的信息重置再

所有操作都需先找到点 对应的根,以根来执行

代码:

  1. //The code is from dxbdly
  2. #include<bits/stdc++.h>
  3. using namespace std;
  4. inline int read() {
  5. int x = 0;
  6. char c = getchar();
  7. bool f = 0;
  8. while(!isdigit(c)) f |= (c == '-'), c = getchar();
  9. while(isdigit(c)) x = (x * 10) + (c ^ 48), c = getchar();
  10. return f ? -x : x;
  11. }
  12. const int maxn = 1e5 + 5, base = 13333;
  13. int n, m, q, res = 1, tot;
  14. int p[maxn];
  15. struct node {
  16. int lc, rc, fa;
  17. int val, rad, siz;
  18. }Tree[maxn];
  19. inline int Rand() {
  20. res *= base; return res;
  21. }
  22. inline int Add(int val) {
  23. tot++, Tree[tot].lc = Tree[tot].rc = 0;
  24. Tree[tot].val = val, Tree[tot].rad = Rand(), Tree[tot].siz = 1, Tree[tot].fa = tot;
  25. return tot;
  26. }
  27. inline void Update(int k) {
  28. Tree[k].siz = Tree[Tree[k].lc].siz + Tree[Tree[k].rc].siz + 1;
  29. }
  30. inline void Split(int k, int &a, int &b, int val) {
  31. if(!k) { a = 0, b = 0; return ; }
  32. if(Tree[k].val <= val) a = k, Split(Tree[k].rc, Tree[k].rc, b, val);
  33. else b = k, Split(Tree[k].lc, a, Tree[k].lc, val);
  34. Update(k);
  35. }
  36. inline void Merge(int &k, int a, int b) {
  37. if(!a || !b) { k = a + b; return ; }
  38. if(Tree[a].rad <= Tree[b].rad) k = a, Tree[b].fa = a, Merge(Tree[k].rc, Tree[k].rc, b);
  39. else k = b, Tree[a].fa = b, Merge(Tree[k].lc, a, Tree[k].lc);
  40. Update(k);
  41. }
  42. inline void Insert(int &k, int c) {
  43. int a, b;
  44. Split(k, a, b, Tree[c].val);
  45. Tree[a].fa = a, Tree[b].fa = b;
  46. Merge(a, a, c), Merge(k, a, b);
  47. }
  48. inline int Get_Val(int k, int x) {
  49. while(Tree[Tree[k].lc].siz + 1 != x) {
  50. if(Tree[Tree[k].lc].siz >= x) k = Tree[k].lc;
  51. else x -= (Tree[Tree[k].lc].siz + 1), k = Tree[k].rc;
  52. }
  53. return p[Tree[k].val];
  54. }
  55. inline int Find(int x) {
  56. if(Tree[x].fa != x) return Tree[x].fa = Find(Tree[x].fa);
  57. return Tree[x].fa;
  58. }
  59. inline int Unnion(int f1, int f2) {
  60. f1 = Find(f1), f2= Find(f2);
  61. if(f1 != f2) Tree[f2].fa = f1;
  62. }
  63. inline void Search(int &x, int now) {
  64. if(!now) return ;
  65. Search(x, Tree[now].lc), Search(x, Tree[now].rc);
  66. Tree[now].lc = Tree[now].rc = 0, Tree[now].siz = 1;
  67. Insert(x, now);
  68. Tree[now].fa = x;
  69. }
  70. inline void Add_New(int u, int v) {
  71. if(Tree[u].siz < Tree[v].siz) swap(u, v);
  72. Search(u, v);
  73. }
  74. int main() {
  75. n = read(), m = read();
  76. for(register int i = 1, x; i <= n; ++i) { x = read(), Add(x), p[x] = i; }
  77. for(register int i = 1; i <= m; ++i) {
  78. int u = Find(read()), v = Find(read());
  79. Add_New(u, v);
  80. }
  81. q = read(); char c;
  82. for(register int i = 1; i <= q; ++i) {
  83. while(!isalpha(c = getchar()));
  84. int x = read(), y = read();
  85. if(c == 'Q') {
  86. x = Find(x);
  87. if(y > Tree[x].siz) { printf("-1\n"); continue; }
  88. printf("%d\n", Get_Val(x, y));
  89. }
  90. else {
  91. x = Find(x), y = Find(y);
  92. if(x != y) Add_New(x, y);
  93. }
  94. }
  95. return 0;
  96. }

思考与总结

很少见的平衡树合并思想,值得积累

另外很多关于合并的算法都能用 优化,算是一个很常用的技巧

包括近期特别流行的 (过两天会写个 ,还在学)

P4146 序列终结者

题面

分析

看到翻转操作,很容易想到平衡树解决

考虑操作 如何实现

对于操作

考虑借用实现翻转操作的技巧,先将区间 出来,打上一个 的标记,在

下传时要注意,左右儿子的 (待会会提到)都要修改

对于操作

首先想到可以用 操作

可我们为了实现翻转操作, 时是按 为关键字的,这样会导致满足 的并不是

考虑其他办法统计。

因为这时 可以划分出区间,所以我们可以考虑在 的同时将这个区间的 求出。

那么在 的时候统计即可,注意 时也要更新这个值。

细节:

当左右儿子为空时,不能对 进行更新,因为 可能为负

代码:

  1. //The code is from dxbdly
  2. #include<bits/stdc++.h>
  3. #define int long long
  4. using namespace std;
  5. inline int read() {
  6. int x = 0;
  7. char c = getchar();
  8. bool f = 0;
  9. while(!isdigit(c)) f |= (c == '-'), c = getchar();
  10. while(isdigit(c)) x = (x * 10) + (c ^ 48), c = getchar();
  11. return f ? -x : x;
  12. }
  13. const int maxn = 1e5 + 5, base = 13333, INF = 1e9 + 7;
  14. int n, m, tot, root, res = 1;
  15. struct node {
  16. int lc, rc, fa;
  17. int val, rad, siz, tag1, tag2, maxx;
  18. }Tree[maxn];
  19. inline int Rand() {
  20. res *= base; return res;
  21. }
  22. inline int Add(int val) {
  23. tot++, Tree[tot].lc = Tree[tot].rc = Tree[tot].tag1 = Tree[tot].tag2 = 0, Tree[tot].maxx = -INF;
  24. Tree[tot].val = val, Tree[tot].rad = Rand(), Tree[tot].siz = 1, Tree[tot].fa = tot;
  25. return tot;
  26. }
  27. inline void Push_Up(int k) {
  28. Tree[k].siz = Tree[Tree[k].lc].siz + Tree[Tree[k].rc].siz + 1;
  29. Tree[k].maxx = Tree[k].val;
  30. if(Tree[k].lc) Tree[k].maxx = max(Tree[k].maxx, Tree[Tree[k].lc].maxx);
  31. if(Tree[k].rc) Tree[k].maxx = max(Tree[k].maxx, Tree[Tree[k].rc].maxx);
  32. }
  33. inline void Push_Down(int k) {
  34. if(Tree[k].tag2) {
  35. if(Tree[k].lc) Tree[Tree[k].lc].tag2 += Tree[k].tag2, Tree[Tree[k].lc].val += Tree[k].tag2, Tree[Tree[k].lc].maxx += Tree[k].tag2;
  36. if(Tree[k].rc) Tree[Tree[k].rc].tag2 += Tree[k].tag2, Tree[Tree[k].rc].val += Tree[k].tag2, Tree[Tree[k].rc].maxx += Tree[k].tag2;
  37. Tree[k].tag2 = 0;
  38. }
  39. if(Tree[k].tag1) {
  40. Tree[Tree[k].lc].tag1 ^= 1, Tree[Tree[k].rc].tag1 ^= 1;
  41. Tree[k].tag1 = 0, swap(Tree[k].lc, Tree[k].rc);
  42. }
  43. }
  44. inline void Split(int k, int &a, int &b, int val) {
  45. if(!k) { a = 0, b = 0; return ; }
  46. Push_Down(k);
  47. if(Tree[Tree[k].lc].siz + 1 <= val) a = k, Split(Tree[k].rc, Tree[k].rc, b, val - Tree[Tree[k].lc].siz - 1);
  48. else b = k, Split(Tree[k].lc, a, Tree[k].lc, val);
  49. Push_Up(k);
  50. }
  51. inline void Merge(int &k, int a, int b) {
  52. if(!a || !b) { k = a + b; return ; }
  53. if(Tree[a].rad <= Tree[b].rad) Push_Down(a), k = a, Tree[b].fa = a, Merge(Tree[k].rc, Tree[k].rc, b);
  54. else Push_Down(b), k = b, Tree[a].fa = b, Merge(Tree[k].lc, a, Tree[k].lc);
  55. Push_Up(k);
  56. }
  57. inline void Insert(int &k, int val) {
  58. int a, b, c = Add(val);
  59. Split(k, a, b, val), Merge(a, a, c), Merge(k, a, b);
  60. }
  61. inline void Update1(int &k, int l, int r) {
  62. int a, b, c;
  63. Split(k, a, c, r), Split(a, a, b, l - 1), Tree[b].tag1 ^= 1, Merge(a, a, b), Merge(k, a, c);
  64. }
  65. inline void Update2(int &k, int l, int r, int val) {
  66. int a, b, c;
  67. Split(k, a, c, r), Split(a, a, b, l - 1), Tree[b].tag2 += val, Tree[b].val += val, Merge(a, a, b), Merge(k, a, c);
  68. }
  69. inline void Query(int l, int r) {
  70. int a, b, c;
  71. Split(root, a, c, r), Split(a, a, b, l - 1);
  72. printf("%lld\n", Tree[b].maxx);
  73. Merge(a, a, b), Merge(root, a, c);
  74. }
  75. signed main() {
  76. n = read(), m = read(), Tree[root].maxx = -INF;
  77. for(register int i = 1; i <= n; ++i) Insert(root, 0);
  78. for(register int i = 1; i <= m; ++i) {
  79. int op = read(), l = read(), r = read(), val;
  80. if(op == 1) val = read(), Update2(root, l, r, val);
  81. if(op == 2) Update1(root, l, r);
  82. if(op == 3) Query(l, r);
  83. }
  84. return 0;
  85. }

思考与总结

在使用平衡树处理区间操作时

考虑先打上标记,然后在 是个很好的思路

同时也让我们看到了平衡树求区间最值的另一种方式。

总的来说, 是一种比较全面的平衡树

它代码量短,同时功能多,可实现 的翻转操作,也可以用来维护

操作的多种方式更是给它的应用增添了更多可能

这也告诫我们,学习数据结构应该深入的研究它与其它数据结构之间的区别,了解它独特的性质及用法,才是最重要的

希望本文能给大家带来帮助,感谢观看。

图片引自博客

本文作者 ,转载或引用请加上原文链接

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