从昨天开始我就想学这个伸展树了,今天花了一个上午2个多小时加下午2个多小时,学习了一下伸展树(Splay树),学习的时候主要是看别人博客啦~发现下面这个博客挺不错的http://zakir.is-programmer.com/posts/21871.html.在里面有连接到《运用伸展树解决数列维护问题》的文章,里面对伸展树的旋转操作讲得很仔细,而且也讲清楚了伸展树是怎么样维护一个数列的,一开始我是小白,觉得树和数列根本没什么关系,但看了之后就会明白,实际上树上的结点是维护该结点的值的,而这个值是原来数列里的哪一项呢?如果该结点对应的中序遍历数k,那么就是对应原数列a中的a[k]这一项.理解了这个之后我就豁然开朗了,要提取一个区间[a,b],实际上只需要将a-1,Splay为根,b+1Splay到根下的右儿子,则根下的右儿子的左儿子就是[a,b]这个区间,这是由平衡树,左小右大的性质决定的.

所以无论做什么,首先是将该区间提取出来,然后对对应结点做就好了.问题是有时a-1不存在,b+1也不存在,所以一开始人为的做两个头尾的结点.而且很多时候为了避免对NULL的特殊处理,我们会构造一个实的null,让它的sz=0;sum=0;这样就不会影响一些情况的处理

伸展树的特性是可以反转,注意到,一棵树,如果我们将它的每个结点的左右儿子都互换一次,它的中序遍历就刚好是原来的中序遍历倒过来,利用这个性质可以实现序列反转.而且还可以添加,假如要添加一个串{b1,b2,b3,b4..}在ak之后的位置,首先调出[ak,ak+1]这个区间,然后将{b1,b2...}建一棵伸展树,然后将结点粘在root->ch[1]的左儿子上即可.删除则是同理.我我还可以提取一个区间出来,反转,再加到我想加的地方.操作都是类似的.

伸展树的优势除了它支持上面的操作外,它还兼容线段树的add,set操作,同样也是每个结点存lazy标记就可以了,然后写一个类似线段树的pushDown,pushUp,维护好区间的信息就可以了~

下面给出的代码很大程度上(90%)是从上面网站的代码上copy下来的,将它改成自己的习惯的变量名,然后自己多写了一个add标记,原来的代码还能求最大子段和,但加了add之后再求就有点麻烦了,所以就删掉了原本维护最大子段和的代码,自己写了个驱动程序,调了一下感觉还行.

代码的参数设置可能会不同,像add()函数传的是从哪个位置(pos),加多少个(tot),大可直接写成l,r,传参数的姿势不同罢了,但注意的是,当要在l位置开始加的时候,传进去的是l+1,是因为前面的头指针占了一位,看到输出之后就大概明白为什么要加1了.对了,因为区间的标记的lazy的,所以直接中序遍历得不出实际的序列(因为有些标记没往下传),所以写了个maintain()先把所有标记下传,实际上是不需要的,随用随查就好了,这么写是为了方便debug~

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cstring>
  4. #include<string>
  5. #include<algorithm>
  6. #include<vector>
  7. #define INF 0x3fffffff
  8. #define maxn 500000
  9. using namespace std;
  10.  
  11. struct Node
  12. {
  13. Node *pre,*ch[];
  14. bool rev,cov; // 结点翻转标记与cover标记
  15. int add; // 结点add标记(表示加了多少)
  16. int sz,val,sum; // 结点的size,保存的值,以及以该结点为子树的和
  17. }*root,N[maxn],*null; // 定义了根的指针,人手写的空的指针,以及结点数组N
  18. Node *stack[maxn]; // 用一个栈来回收用过的指针,这是学到的新姿势,这样的话在构造新的结点的时候可以不用一直idx++
  19. int top,idx; // 栈顶指针,以及数组idx指针
  20. int a[maxn+]; // 用来构造伸展树的数组
  21.  
  22. Node *addNode(int val) // 产生新结点
  23. {
  24. Node *p;
  25. if(top) p=stack[--top]; // 首先从回收栈里取
  26. else p=&N[idx++]; // 没有的话从N里面取
  27. //初始化
  28. p->rev=p->cov=false;
  29. p->sz=;
  30. p->sum=p->val=val;
  31. p->ch[]=p->ch[]=p->pre=null;
  32. return p;
  33. }
  34.  
  35. void Recycle(Node *p) // 递归回收删除掉的指针,这是用来节省空间的
  36. {
  37. if(p->ch[]!=null) Recycle(p->ch[]);
  38. if(p->ch[]!=null) Recycle(p->ch[]);
  39. stack[++top]=p;
  40. }
  41.  
  42. void pushDown(Node *p) // 核心函数,用来处理标记的
  43. {
  44. if(p==null||!p) return; // 遇到空指针返回
  45. if(p->rev) // 先处理反转标记
  46. {
  47. swap(p->ch[],p->ch[]); // 交换子树
  48. if(p->ch[]!=null) p->ch[]->rev^=; // 标记下传
  49. if(p->ch[]!=null) p->ch[]->rev^=; // 标记下传
  50. p->rev=false; // 标记取消
  51. }
  52. if(p->cov) //下面的cov和add标记的更新与下传与线段树相同
  53. {
  54. if(p->ch[]!=null){
  55. p->ch[]->val=p->val;
  56. p->ch[]->sum=p->val*p->ch[]->sz;
  57. p->ch[]->cov=true;
  58. p->ch[]->add=;
  59. }
  60. if(p->ch[]!=null){
  61. p->ch[]->val=p->val;
  62. p->ch[]->sum=p->val*p->ch[]->sz;
  63. p->ch[]->cov=true;
  64. p->ch[]->add=;
  65. }
  66. p->cov=false;
  67. }
  68. if(p->add)
  69. {
  70. if(p->ch[]!=null){
  71. p->ch[]->val+=p->add;
  72. p->ch[]->sum+=p->ch[]->sz*p->add;
  73. p->ch[]->add+=p->add;
  74. }
  75. if(p->ch[]!=null){
  76. p->ch[]->val+=p->add;
  77. p->ch[]->sum+=p->ch[]->sz*p->add;
  78. p->ch[]->add+=p->add;
  79. }
  80. p->add=;
  81. }
  82. }
  83.  
  84. void pushUp(Node *p) // 核心函数,维护信息
  85. {
  86. if(p==null) return;
  87. pushDown(p);
  88. pushDown(p->ch[]);
  89. pushDown(p->ch[]);
  90. p->sz=p->ch[]->sz+p->ch[]->sz+;
  91. p->sum=p->val+p->ch[]->sum+p->ch[]->sum;
  92. }
  93.  
  94. void rotate(Node *x,int c) // Splay树的旋转函数,标准姿势
  95. {
  96. Node *y=x->pre;
  97. pushDown(y);pushDown(x);
  98. y->ch[c^]=x->ch[c];
  99. if(x->ch[c]!=null)
  100. x->ch[c]->pre=y;
  101. x->pre=y->pre;
  102. if(y->pre!=null)
  103. if(y->pre->ch[]==y)
  104. y->pre->ch[]=x;
  105. else
  106. y->pre->ch[]=x;
  107. x->ch[c]=y;y->pre=x;
  108. if(y==root) root=x;
  109. pushUp(y);
  110. }
  111.  
  112. void Splay(Node *x,Node *f) // 将x结点转到f下
  113. {
  114. pushDown(x);
  115. while(x->pre!=f)
  116. {
  117. Node *y=x->pre,*z=y->pre;
  118. if(x->pre->pre==f)
  119. rotate(x,x->pre->ch[]==x);
  120. else
  121. {
  122. if(z->ch[]==y){
  123. if(y->ch[]==x) {rotate(y,);rotate(x,);}
  124. else {rotate(x,);rotate(x,);}
  125. }
  126. else{
  127. if(y->ch[]==x) {rotate(y,),rotate(x,);}
  128. else {rotate(x,),rotate(x,);}
  129. }
  130. }
  131. }
  132. pushUp(x);
  133. }
  134.  
  135. Node *select(int kth) // 选出第k个点,返回对应结点
  136. {
  137. int tmp;
  138. Node *t=root;
  139. while(){
  140. pushDown(t);
  141. tmp=t->ch[]->sz;
  142. if(tmp+==kth) break;
  143. if(kth<=tmp) {t=t->ch[];}
  144. else { kth-=tmp+;t=t->ch[];}
  145. }
  146. return t;
  147. }
  148.  
  149. Node *build(int L,int R) // 建树,有点像线段树
  150. {
  151. if(L>R) return null;
  152. int M=(L+R)>>;
  153. Node *p=addNode(a[M]);
  154. p->ch[]=build(L,M-);
  155. if(p->ch[]!=null){
  156. p->ch[]->pre=p;
  157. }
  158. p->ch[]=build(M+,R);
  159. if(p->ch[]!=null){
  160. p->ch[]->pre=p;
  161. }
  162. pushUp(p);
  163. }
  164.  
  165. void remove(int pos,int tot) // 从pos位置开始,删除tot个(包括pos)
  166. {
  167. Splay(select(pos-),null);
  168. Splay(select(pos+tot),root);
  169. if(root->ch[]->ch[]!=null){
  170. Recycle(root->ch[]->ch[]);
  171. root->ch[]->ch[]=null;
  172. }
  173. pushUp(root->ch[]);pushUp(root);
  174. Splay(root->ch[],null);
  175. }
  176.  
  177. void insert(int pos,int tot) // 添加,插的是一个数组的时候,要在数组a里面建一颗树,即a[1~N]是要插的数
  178. {
  179. Node *troot=build(,tot);
  180. Splay(select(pos),null);
  181. Splay(select(pos+),root);
  182. root->ch[]->ch[]=troot;
  183. troot->pre=root->ch[];
  184. pushUp(root->ch[]);pushUp(root);
  185. Splay(troot,null);
  186. }
  187.  
  188. void reverse(int pos,int tot) // 从pos开始翻转tot个
  189. {
  190. Splay(select(pos-),null);
  191. Splay(select(pos+tot),root);
  192. if(root->ch[]->ch[]!=null)
  193. {
  194. root->ch[]->ch[]->rev^=;
  195. Splay(root->ch[]->ch[],null);
  196. }
  197. }
  198.  
  199. void set(int pos,int tot,int c) // 从pos开始将tot个设置为c
  200. {
  201. Splay(select(pos-),null);
  202. Splay(select(pos+tot),root);
  203. root->ch[]->ch[]->val=c;
  204. root->ch[]->ch[]->sum=root->ch[]->ch[]->sz*c;
  205. root->ch[]->ch[]->cov=true;
  206. Splay(root->ch[]->ch[],null);
  207. }
  208.  
  209. void add(int pos,int tot,int c) // 从pos开始将tot个加c
  210. {
  211. Splay(select(pos-),null);
  212. Splay(select(pos+tot),root);
  213. root->ch[]->ch[]->val+=c;
  214. root->ch[]->ch[]->sum+=c*root->ch[]->ch[]->sz;
  215. root->ch[]->ch[]->add+=c;
  216. Splay(root->ch[]->ch[],null);
  217. }
  218.  
  219. int query(int pos,int tot) // 求pos开始tot个的和
  220. {
  221. Splay(select(pos-),null);
  222. Splay(select(pos+tot),root);
  223. return root->ch[]->ch[]->sum;
  224. }
  225.  
  226. void init() // 初始化函数
  227. {
  228. idx=top=; // idx,top归零
  229. null=addNode(-INF); // 初始化空指针
  230. null->sz=null->sum=; // 记住sz和sum一定要设为0
  231. root=addNode(-INF); // 初始化根指针
  232. root->sum=;
  233. Node *p;
  234. p=addNode(-INF); // 初始化"树尾"的指针
  235. root->ch[]=p;
  236. p->pre=root;
  237. p->sum=;
  238. pushUp(root->ch[]);
  239. pushUp(root);
  240. }
  241. //下面三个函数是调试的时候用的
  242. void maintain(Node *p) // 因为标记是lazy的,所以先将所有标记都下传好
  243. {
  244. pushDown(p);
  245. if(p->ch[]!=null) maintain(p->ch[]);
  246. if(p->ch[]!=null) maintain(p->ch[]);
  247. }
  248. void dfs(Node *x) // 中序遍历
  249. {
  250. if(x==null) return;
  251. dfs(x->ch[]);
  252. printf("%d ",x->val);
  253. dfs(x->ch[]);
  254. }
  255. void print() // 打印
  256. {
  257. maintain(root);
  258. dfs(root);
  259. puts("");
  260. }
  261.  
  262. int main()
  263. {
  264. int n,m;
  265. while(cin>>n)
  266. {
  267. for(int i=;i<=n;i++){
  268. scanf("%d",&a[i]);
  269. }
  270. init();
  271. Node *troot=build(,n); // 从a数组建一颗Splay树
  272. root->ch[]->ch[]=troot; // 让它和init()里的root,p连上
  273. troot->pre=root->ch[];
  274. pushUp(root->ch[]); // 维护相关信息
  275. pushUp(root->ch[]);
  276. cin>>m;
  277. int o,l,r,v;
  278. //支持六种操作,区间add,区间set,区间反转,区间删除,区间添加,区间和
  279. while(m--)
  280. {
  281. scanf("%d",&o);
  282. if(o==){
  283. scanf("%d%d%d",&l,&r,&v);add(l+,r-l+,v);print();
  284. }
  285. else if(o==){
  286. scanf("%d%d%d",&l,&r,&v);set(l+,r-l+,v);print();
  287. }
  288. else if(o==){
  289. scanf("%d%d",&l,&r);reverse(l+,r-l+);print();
  290. }
  291. else if(o==){
  292. scanf("%d%d",&l,&r);remove(l+,r-l+);print();
  293. }
  294. else if(o==){
  295. scanf("%d%d",&l,&v);
  296. for(int i=;i<=v;i++){ scanf("%d",&a[i]);}
  297. insert(l+,v);
  298. print();
  299. }
  300. else if(o==){
  301. scanf("%d%d",&l,&r);
  302. cout<<query(l+,r-l+)<<endl;
  303. }
  304. }
  305. }
  306. return ;
  307. }

暑假学习日记:Splay树的更多相关文章

  1. Splay树再学习

    队友最近可能在学Splay,然后让我敲下HDU1754的题,其实是很裸的一个线段树,不过用下Splay也无妨,他说他双旋超时,单旋过了,所以我就敲来看下.但是之前写的那个Splay越发的觉得不能看,所 ...

  2. 文艺平衡Splay树学习笔记(2)

    本blog会讲一些简单的Splay的应用,包括但不局限于 1. Splay 维护数组下标,支持区间reserve操作,解决区间问题 2. Splay 的启发式合并(按元素多少合并) 3. 线段树+Sp ...

  3. Splay树学习

    首先给出一论文讲的很好: http://www.docin.com/p-63165342.html http://www.docin.com/p-62465596.html 然后给出模板胡浩大神的模板 ...

  4. 省选算法学习-数据结构-splay

    于是乎,在丧心病狂的noip2017结束之后,我们很快就要迎来更加丧心病狂的省选了-_-|| 所以从写完上一篇博客开始到现在我一直深陷数据结构和网络流的漩涡不能自拔 今天终于想起来写博客(只是懒吧.. ...

  5. Linux学习日记-使用EF6 Code First(四)

    一.在linux上使用EF 开发环境 VS2013+mono 3.10.0 +EF 6.1.0 先检测一下EF是不是6的 如果不是  请参阅 Linux学习日记-EF6的安装升级(三) 由于我的数据库 ...

  6. Splay树-Codevs 1296 营业额统计

    Codevs 1296 营业额统计 题目描述 Description Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况. Tiger拿出了公司 ...

  7. ZOJ3765 Lights Splay树

    非常裸的一棵Splay树,需要询问的是区间gcd,但是区间上每个数分成了两种状态,做的时候分别存在val[2]的数组里就好.区间gcd的时候基本上不支持区间的操作了吧..不然你一个区间里加一个数gcd ...

  8. 1439. Battle with You-Know-Who(splay树)

    1439 路漫漫其修远兮~ 手抄一枚splay树 长长的模版.. 关于spaly树的讲解   网上很多随手贴一篇 貌似这题可以用什么bst啦 堆啦 平衡树啦 等等 这些本质都是有共同点的 查找.删除特 ...

  9. android学习日记05--Activity间的跳转Intent实现

    Activity间的跳转 Android中的Activity就是Android应用与用户的接口,所以了解Activity间的跳转还是必要的.在 Android 中,不同的 Activity 实例可能运 ...

随机推荐

  1. 南阳理工ACM1076--方案数量

    题目地址:http://acm.nyist.net/JudgeOnline/problem.php?pid=1076 分析: <span style="font-size:18px;& ...

  2. Linux读写锁的使用

    读写锁是用来解决读者写者问题的,读操作可以共享,写操作是排它的,读可以有多个在读,写只有唯一个在写,写的时候不允许读. 具有强读者同步和强写者同步两种形式: 强读者同步:当写者没有进行写操作时,读者就 ...

  3. 在 linux x86-64 模式下分析内存映射流程

    前言 在上一篇中我们分析了 linux 在 x86-32 模式下的虚拟内存映射流程,本章主要继续分析 linux 在 x86-64 模式下的虚拟内存映射流程. 讨论的平台是 x86-64, 也可以称为 ...

  4. IntellijIDEA 使用技巧

    1:显示工具栏目  toolbar:view ->ToolBar 2:加载源码   new project ->选择java project ->选择源码所在目录 ->ok

  5. ThreadLocal学习记录

    ThreadLocal简介 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的 ...

  6. 鼠标悬浮图片时弹出透明提示图层的jQuery特效

    源码: <!doctype html> <html class="no-js" lang="en"> <head> < ...

  7. zip生成

    生成zip文件官方网站:http://www.phpconcept.net/pclzip/ 用法一: 1 <?php 2     include_once('pclzip.lib.php'); ...

  8. 高德amap 根据坐标获取的地址信息

    高德地理逆地理编码接口List<List<Address>> lists = coder.getFromLocation(33.00, 116.500, 3, 3, 3, 50 ...

  9. PHP中include和require绝对路径、相对路径问题

    在写PHP程序时,经常要用到include或require包含其他文件,但是各文件里包含的文件多了之后,就会产生路径问题. 如下目录: <web>(网站根目录) ├<A>文件夹 ...

  10. WPF中实现Button.Content变化的简易动画

    项目中曾要这样的需求——输入法的切换,要求从English切换到简体中文的时候,Button的Content先从English变成空白,再从空白变成简体中文, 而不是直接从English变成简体中文. ...