经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C++ » 查看文章
【阶梯报告】洛谷P3391【模板】文艺平衡树 splay
来源:cnblogs  作者:帅小子F$  时间:2018/11/19 9:24:48  对本文有异议

【阶梯报告】洛谷P3391【模板】文艺平衡树 splay

题目链接在这里[链接](https://www.luogu.org/problemnew/show/P3391)
最近在学习splay,终于做对了这道模版题,虽然不是很难的样子。~~但是我一开始并不会做,而且看完题解之后还打错一直打不对,调试了很久~~
下面是题目简述

现在给你一个长度为n的序列,序列元素初始为1,2,3...n,同时有m个操作,每个操作给定一个L和R,表示将[L,R]区间的数进行翻转。
输出:完成所有操作之后的序列(n,m≤100000)

首先,这道题用暴力是肯定超时的。但是既然连题目都提示用splay做这道题了,那么我们自然可以用splay做这道题。
首先我们想一下splay的树的性质。
### 性质
1. 如果我们给每一个点添加一个键值,这个键值表示的是这个点在序列中的位置,那么我们对splay树进行中序遍历之后,得到的序列就是原来对应的序列。
2. 对于splay树,无论是进行zip还是zap操作,都不会影响最终中序遍历的结果。因为zip和zap都是在保证二叉查找树的性质的前提下进行的。
3. 假如我们splay(L-1,root),然后,splay(R+1,root->right_child),那么我们就会惊奇地发现,区间[L,R]内的所有点都在R+1这个结点的左子树种。
4. 假如我们把任意一个结点的子树中的每一个结点的左右子树都换边,(包括自己)那么最终中序遍历的结果就是——除了被翻转的子树以外,其他所有点的中序遍历都没有变化,而子树中的所有节点(包括子树根节点)的中序遍历刚刚好反了过来。(你们可以自己画一个图试一下,或者说,如下图)

 


思路
第一种简陋的想法
1. 以1,2,3,4...n为键值插入splay树(或者直接构建一棵平衡树就行,因为你已经知道会有哪些元素插入树中)。之后,我们对树的所有操作都不需要用到键值,直接根据zip和zap不会改变中序遍历的特性进行改变。
2. 翻转区间的时候,先splay(l-1,root),splay(r+1,root->right_child),然后再把r+1这个结点的左子树的所有左右孩子交换即可。

虽然这么做确实可以做对题目,但是这样会有几个需要注意的地方。
1 . 假如要reserve(1,n),那么我们将会splay(0,root)和splay(n+1,root->left_child),但是树中没有0和n+1这两个结点。因此一开始的时候我们就应该加入INF和-INF,充当卫兵的作用。
2 . 交换子树中所有结点的速度实在是太慢了,和o(n)压根没有区别。这样根本就没有变快。
优化的方法
1. 翻转树的时候不需要把整个子树都交换,只要采取线段树的懒标记的方法做一个标记即可。
- 标记完之后,假如等一下再标记同样的区间,只要异或一下1就可以了
- 假如要splay的点在已经翻转过的子树里面,那么我们到时候只要在寻找结点的过程中进行push_down就可以了。
- 在输出整棵splay树的时候,也注意一下push_down就可以了。
具体方法
1. 把所有的元素都塞进splay里面。由于一开始就是有序的,所以你既可以一个一个地insert进去,也可以直接就构建一个十分平衡的splay树
2. 对于每一个翻转操作,首先splay(l-1,root),splay(r+1,root->right_child),然后再把r+1的左子树打上标记即可
3. 要点:每次在find一个结点的时候,一定要先push_down,然后再进行操作。
4. 最后输出整棵树的时候也注意一下push_down
下面是我的代码,可能不是很看得明白。

  1. 1 #include<cstdio>
  2. 2 using namespace std;
  3. 3 const int N=100010;
  4. 4 const int INF=100000000;
  5. 5 struct node
  6. 6 {
  7. 7 int lc,rc,fa,size,val,mark;//mark就是懒标记,val是本来的值,size是子树(包括自己)的大小
  8. 8 node()
  9. 9 {
  10. 10 lc=rc=fa=size=val=mark=0;
  11. 11 }
  12. 12 }tree[N];
  13. 13 int root=1,tot=1,FIRST=1;
  14. 14 void build(int,int,int,int);//直接用递归在刚开始的时候建立一棵比较平衡的树
  15. 15 void push_down(int);
  16. 16 void zip(int);//左旋
  17. 17 void zap(int);//右旋
  18. 18 void initailize();//放入INT和-INF作为卫兵
  19. 19 void splay(int,int);
  20. 20 int find(int);//用于寻找splay树中的第几大的数。毕竟在序列中排第k的数就是在splay中第k大的数
  21. 21 void reverse(int,int);
  22. 22 void print(int);//用递归输出整棵树
  23. 23 int main()
  24. 24 {
  25. 25 int n,m;
  26. 26 scanf("%d%d",&n,&m);
  27. 27 build(1,1,n,0);
  28. 28 initailize();
  29. 29 while(m--)
  30. 30 {
  31. 31 int l,r;
  32. 32 scanf("%d%d",&l,&r);
  33. 33 reverse(l+1,r+1);//由于有-INF的存在,所以翻转[l,r]区间的时候,其实是翻转[l+1,r+1]区间
  34. 34 }
  35. 35 print(root);
  36. 36 return 0;
  37. 37 }
  38. 38 void build(int x,int l,int r,int father)
  39. 39 {
  40. 40 tree[x].fa=father;
  41. 41 tree[x].size=1;
  42. 42 if(l==r)
  43. 43 {
  44. 44 tree[x].val=l;
  45. 45 return;
  46. 46 }
  47. 47 int mid=(l+r)/2;
  48. 48 tree[x].val=mid;
  49. 49 if(mid==l)
  50. 50 {
  51. 51 tree[x].rc=++tot;
  52. 52 build(tot,r,r,x);
  53. 53 tree[x].size+=tree[tree[x].rc].size;
  54. 54 }
  55. 55 else
  56. 56 {
  57. 57 tree[x].lc=++tot;
  58. 58 build(tot,l,mid-1,x);
  59. 59 tree[x].rc=++tot;
  60. 60 build(tot,mid+1,r,x);
  61. 61 tree[x].size+=tree[tree[x].lc].size+tree[tree[x].rc].size;
  62. 62 }
  63. 63 }
  64. 64 void initailize()
  65. 65 {
  66. 66 int p=root;
  67. 67 while(tree[p].lc!=0) tree[p].size++,p=tree[p].lc;
  68. 68 tree[p].size++;
  69. 69 tree[p].lc=++tot;
  70. 70 tree[tot].fa=p;
  71. 71 tree[tot].size=1;
  72. 72 tree[tot].val=-INF;
  73. 73 p=root;
  74. 74 while(tree[p].rc!=0) tree[p].size++,p=tree[p].rc;
  75. 75 tree[p].size++;
  76. 76 tree[p].rc=++tot;
  77. 77 tree[tot].fa=p;
  78. 78 tree[tot].size=1;
  79. 79 tree[tot].val=INF;
  80. 80 }
  81. 81 void inline push_down(int x)
  82. 82 {
  83. 83 if(tree[x].mark)
  84. 84 {
  85. 85 int L=tree[x].lc,R=tree[x].rc;
  86. 86 tree[L].mark^=1;
  87. 87 tree[R].mark^=1;
  88. 88 tree[x].mark=0;
  89. 89 tree[x].lc=R;
  90. 90 tree[x].rc=L;
  91. 91 }
  92. 92 }
  93. 93 void zip(int x)
  94. 94 {
  95. 95 int y=tree[x].fa;
  96. 96 tree[y].rc=tree[x].lc;
  97. 97 tree[x].lc=y;
  98. 98 tree[x].fa=tree[y].fa;
  99. 99 tree[y].fa=x;
  100. 100 if(tree[x].fa)
  101. 101 if(tree[tree[x].fa].lc==y)
  102. 102 tree[tree[x].fa].lc=x;
  103. 103 else tree[tree[x].fa].rc=x;
  104. 104 else root=x;
  105. 105 if(tree[y].rc)
  106. 106 tree[tree[y].rc].fa=y;
  107. 107 tree[y].size=tree[tree[y].lc].size+tree[tree[y].rc].size+1;
  108. 108 tree[x].size=tree[tree[x].lc].size+tree[tree[x].rc].size+1;
  109. 109 }
  110. 110 void zap(int x)
  111. 111 {
  112. 112 int y=tree[x].fa;
  113. 113 tree[y].lc=tree[x].rc;
  114. 114 tree[x].rc=y;
  115. 115 tree[x].fa=tree[y].fa;
  116. 116 tree[y].fa=x;
  117. 117 if(tree[x].fa)
  118. 118 if(tree[tree[x].fa].lc==y)
  119. 119 tree[tree[x].fa].lc=x;
  120. 120 else tree[tree[x].fa].rc=x;
  121. 121 else root=x;
  122. 122 if(tree[y].lc)
  123. 123 tree[tree[y].lc].fa=y;
  124. 124 tree[y].size=tree[tree[y].lc].size+tree[tree[y].rc].size+1;
  125. 125 tree[x].size=tree[tree[x].lc].size+tree[tree[x].rc].size+1;
  126. 126 }
  127. 127 void splay(int x,int aim)
  128. 128 {
  129. 129 aim=tree[aim].fa;
  130. 130 while(tree[x].fa!=aim)
  131. 131 {
  132. 132 int y=tree[x].fa;
  133. 133 int z=tree[y].fa;
  134. 134 if(z==aim)
  135. 135 if(tree[y].lc==x)
  136. 136 zap(x);
  137. 137 else zip(x);
  138. 138 else if(tree[z].lc==y&&tree[y].lc==x)
  139. 139 zap(x),zap(x);
  140. 140 else if(tree[z].rc==y&&tree[y].rc==x)
  141. 141 zip(x),zip(x);
  142. 142 else if(tree[z].lc==y)
  143. 143 zip(x),zap(x);
  144. 144 else zap(x),zip(x);
  145. 145 }
  146. 146 }
  147. 147 int find(int k)
  148. 148 {
  149. 149 int p=root;
  150. 150 while(1)
  151. 151 {
  152. 152 push_down(p);
  153. 153 if(tree[tree[p].lc].size>=k)
  154. 154 p=tree[p].lc;
  155. 155 else
  156. 156 {
  157. 157 k-=tree[tree[p].lc].size;
  158. 158 if(k==1) return p;
  159. 159 k-=1;
  160. 160 p=tree[p].rc;
  161. 161 }
  162. 162 }
  163. 163 }
  164. 164 void reverse(int l,int r)
  165. 165 {
  166. 166 int L=find(l-1);
  167. 167 splay(L,root);
  168. 168 int R=find(r+1);
  169. 169 splay(R,tree[L].rc);
  170. 170 tree[tree[R].lc].mark^=1;
  171. 171 }
  172. 172 void print(int x)
  173. 173 {
  174. 174 if(x==0) return;
  175. 175 push_down(x);
  176. 176 print(tree[x].lc);
  177. 177 if(tree[x].val!=INF&&tree[x].val!=-INF)
  178. 178 {
  179. 179 if(FIRST)
  180. 180 {
  181. 181 FIRST=0;
  182. 182 printf("%d",tree[x].val);
  183. 183 }
  184. 184 else printf(" %d",tree[x].val);
  185. 185 }
  186. 186 print(tree[x].rc);
  187. 187 }

对了,顺便说一下,为什么我看的资料书里面,不同的平衡树的zip是不一样的?有些是表示左旋,有些是表示右旋,zap也是一样,导致我现在都是按照自己的标准来了。

我在CSDN也发了。https://blog.csdn.net/Liang_Si_FFF/article/details/84190616

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号