手动博客搬家: 本文发表于20180825 00:34:49, 原地址https://blog.csdn.net/suncongbo/article/details/82027387

题目链接: (luogu) https://www.luogu.org/problem/show?pid=2042

(bzoj) https://www.lydsy.com/JudgeOnline/problem.php?id=1500

思路分析:

这个题嘛。。思路没啥好说的

用splay每个点维护四个量:\(sum[0..3]\), \(sum[3]\)表示splay整个子树代表的区间内元素之和;\(sum[1]\)和\(sum[2]\)分别表示这个区间内以左、右端点开始的元素最大和;\(sum[0]\)表示这个区间内(不限端点)的最大子段和。

比如:序列是

  1. a: 1 -4 -2 9 -5 -7 -999 666 -999 3 0
  2. sum[3]=1+(-4)+(-2)+8+(-5)+(-7)+(-999)+666+(-999)+3+0=-1338
  3. sum[2]=3+0=3
  4. sum[1]=1+(-4)+(-2)+9=4
  5. sum[0]=666=666

区间合并的话,我们可以先想想线段树怎么合并两段区间,分类讨论即可。平衡树由于根节点上还有值,因此合并两段区间+一个值,稍微麻烦点。(这部分略去,不会的可以去做bzoj 1756)

然后就可以开心地码啦!

部分易错点
  1. 由于所有插入的元素可能达到\(4\times 10^6\)个, 如果建这么多个splay节点,每一个开\(int\)数组记录,则每个节点维护每个值就会花\(16MB\)空间,然而空间限制\(128MB\), 也就是我们至多维护\(7\)个量。(什么你说8个??你\(128MB\)空间全开了这一个数组,多开一个字节就MLE了啊)而至少我没有想出用每个节点\(7\)个量维护的方法。貌似开\(fa, son[2], sum[4], tag\)就已经\(8\)个了啊..

    解决办法: 手写内存回收池, 对于已经删除的节点,把它\(clear\)掉并把编号放到一个内存回收池中,insert时先从内存回收池中取出一个编号来用,如果内存回收池为空再开新节点。这样可以保证平衡树的大小约等于当前序列的大小,因此开\(5\times 10^5\)即可。
  2. 由于插入的次数虽然少,但是插入的元素总数是很多的 (一次插入多个)。如果一个一个地插,会导致每次都要\(O(\log n*tot)\), 还带着splay这么大的常数,\(4\times 10^6\)的规模显然是无法承受的。

    解决办法:先在\(O(tot)\)的时间内把加入的那\(tot\)个节点建出一棵新的完全BST,然后把\(posi\) splay到根, \(posi+1\) splay到根的右儿子,此时根的右儿子的左儿子为空,把新的平衡树挂到根的右儿子的左儿子上即可。同时注意内存回收池的使用。删除也是类似。删除的时候,首先把删除的节点一起放到根的右儿子的左儿子上,然后\(O(tot)\)地遍历这棵子树,把里面的节点\(clear\)掉并放入内存回收池。
  3. 有个地方题面说的不明白: \(MAX-SUM\)操作选出来的子列要非空。

    因此碰到了全是负数的整个序列,答案应该是绝对值最小的那个,而不是\(0\).

    解决办法: 首先,正常节点的\(sum[0..3], val, tag\)都要设成\(-INF\)而不是\(0\). 根据splay常识,对区间\([l,r]\)单独拎出来进行操作时我们先把\(l-1\) splay到根,再把\(r+1\) splay到根的右儿子。因此为了避免\(l-1\)和\(r+1\)合法,我们可以把要处理的区间平移一位变成\([2,n+1]\), 而\(1\)号点和\((n+2)\)号点作为缓冲点。如果这两个点的\(sum[0..3], val, tag\)不慎设成了0, 则也会导致\(MAX-SUM\)无法处理答案为负(因为程序自动默认两个缓冲点是和最大的子列)。因此无论是正常点还是缓冲点都应该初值赋为\(-INF\). (否则洛谷\(90\)分)
  4. 本题有个极坑之处,\(GET-SUM\)操作的\(tot\)可能为\(0\)!

    解决办法: 特判 (否则洛谷\(80\)分)
前四条是客观吐槽,后几条就是我自己犯的若干sb问题了
大概是写出了锅*7, 我好菜啊
  1. 建树时没有分清原数组中的下标和\(splay\)中的编号。

    详见代码。build函数中的mid是原数组,pos是节点编号,而cfa,是父亲节点在原数组中的编号。(有点乱。。)
  2. 在\(REVERSE\)操作之后没有交换\(sum[1]\)和\(sum[2]\)并\(pushup\).

    由于我们维护的是最大子段和,如果左右子节点被交换,那么\(sum[1]\)和\(sum[2]\)也随之交换。(可以认为节点的加法,即区间合并,不满足交换律)因此在\(REVERSE\)打标记的同时应当交换两个儿子以及该节点的\(sum[1]\)和\(sum[2]\), 并\(pushup\).同时,在pushdown时如果有\(reverse\)标记,也要交换当前节点的\(sum[1]\)和\(sum[2]\)
  3. 为了偷懒减少代码长度,\(sum[0]\)的合并少考虑了一种情况。(原地爆炸...以后再也不偷懒了呜呜呜)

好吧再多也没得说了,反正这道题尽管很毒瘤,但也是练习Splay的一道经典码农题,以后一定一定要抽空多写几遍!

怎么跑得这么慢啊...luogu不开O2要排后100了,bzoj开O2, 2137人AC我排1300多呜呜呜

代码实现

(luogu: 4399 ms without O2; bzoj: 5912 ms)

  1. #include<cstdio>
  2. #include<algorithm>
  3. #include<cstring>
  4. #define llong long long
  5. using namespace std;
  6. const int SZ = 5e5;
  7. const int N = 4e6;
  8. const int INF = 6e8;
  9. struct SplayNode
  10. {
  11. int fa,son[2],tag,sum[4],sz,val;
  12. bool rev;
  13. SplayNode() {fa = son[0] = son[1] = rev = val = sz = 0; tag = sum[0] = sum[1] = sum[2] = sum[3] = -INF;}
  14. void clear() {fa = son[0] = son[1] = rev = val = sz = 0; tag = sum[0] = sum[1] = sum[2] = sum[3] = -INF;}
  15. } spl[SZ+4],tmp[SZ+4];
  16. int ids[N+4];
  17. int id[SZ+4];
  18. int a[SZ+4];
  19. char opt[14];
  20. int n,q,siz,rtn,tp;
  21. int newnode()
  22. {
  23. if(tp>0) {int ret = ids[tp]; ids[tp] = 0; tp--; return ret;}
  24. else {siz++; return siz;}
  25. }
  26. void pushup(int pos) //这里有简化很多的写法,推荐看洛谷题解
  27. {
  28. if(pos==0) return;
  29. int ls = spl[pos].son[0],rs = spl[pos].son[1];
  30. if(ls==0 && rs==0) {spl[pos].sz = 1; spl[pos].sum[0] = spl[pos].sum[1] = spl[pos].sum[2] = spl[pos].sum[3] = spl[pos].val; return;}
  31. if(ls==0 && rs!=0)
  32. {
  33. spl[pos].sz = spl[rs].sz+1;
  34. spl[pos].sum[3] = spl[rs].sum[3]+spl[pos].val;
  35. spl[pos].sum[2] = max(spl[pos].sum[3],spl[rs].sum[2]);
  36. spl[pos].sum[1] = max(spl[pos].sum[3],max(spl[pos].val,spl[pos].val+spl[rs].sum[1]));
  37. spl[pos].sum[0] = max(max(spl[pos].sum[1],spl[pos].sum[2]),spl[rs].sum[0]);
  38. return;
  39. }
  40. if(ls!=0 && rs==0)
  41. {
  42. spl[pos].sz = spl[ls].sz+1;
  43. spl[pos].sum[3] = spl[ls].sum[3]+spl[pos].val;
  44. spl[pos].sum[2] = max(spl[pos].sum[3],max(spl[pos].val,spl[pos].val+spl[ls].sum[2]));
  45. spl[pos].sum[1] = max(spl[pos].sum[3],spl[ls].sum[1]);
  46. spl[pos].sum[0] = max(max(spl[pos].sum[1],spl[pos].sum[2]),spl[ls].sum[0]);
  47. return;
  48. }
  49. spl[pos].sz = spl[ls].sz+spl[rs].sz+1;
  50. spl[pos].sum[3] = spl[ls].sum[3]+spl[pos].val+spl[rs].sum[3];
  51. spl[pos].sum[2] = max(max(spl[pos].sum[3],spl[rs].sum[2]),spl[rs].sum[3]+spl[pos].val+(spl[ls].sum[2]>0 ? spl[ls].sum[2] : 0));
  52. spl[pos].sum[1] = max(max(spl[pos].sum[3],spl[ls].sum[1]),spl[ls].sum[3]+spl[pos].val+(spl[rs].sum[1]>0 ? spl[rs].sum[1] : 0));
  53. spl[pos].sum[0] = max(max(max(spl[pos].sum[1],spl[pos].sum[2]),max(spl[ls].sum[0],spl[rs].sum[0])),max(max(spl[pos].val,spl[ls].sum[2]+spl[pos].val+spl[rs].sum[1]),max(spl[pos].val+spl[ls].sum[2],spl[pos].val+spl[rs].sum[1])));
  54. }
  55. void pushdown(int pos)
  56. {
  57. if(pos==0) return;
  58. int ls = spl[pos].son[0],rs = spl[pos].son[1];
  59. if(ls==0 && rs==0) {spl[pos].tag = -INF; spl[pos].rev = 0; return;}
  60. if(spl[pos].tag>-INF)
  61. {
  62. if(ls!=0)
  63. {
  64. spl[ls].tag = spl[pos].tag; spl[ls].val = spl[ls].tag;
  65. spl[ls].sum[3] = spl[ls].tag*spl[ls].sz;
  66. spl[ls].sum[0] = spl[ls].sum[1] = spl[ls].sum[2] = spl[ls].tag>0 ? spl[ls].tag*spl[ls].sz : spl[ls].tag;
  67. }
  68. if(rs!=0)
  69. {
  70. spl[rs].tag = spl[pos].tag; spl[rs].val = spl[rs].tag;
  71. spl[rs].sum[3] = spl[rs].tag*spl[rs].sz;
  72. spl[rs].sum[0] = spl[rs].sum[1] = spl[rs].sum[2] = spl[rs].tag>0 ? spl[rs].tag*spl[rs].sz : spl[rs].tag;
  73. }
  74. spl[pos].tag = -INF;
  75. }
  76. if(spl[pos].rev==true)
  77. {
  78. if(ls!=0) {spl[ls].rev ^= 1; swap(spl[ls].son[0],spl[ls].son[1]); swap(spl[ls].sum[1],spl[ls].sum[2]);}
  79. if(rs!=0) {spl[rs].rev ^= 1; swap(spl[rs].son[0],spl[rs].son[1]); swap(spl[rs].sum[1],spl[rs].sum[2]);}
  80. spl[pos].rev = 0;
  81. }
  82. }
  83. void rotate(int x,bool dir)
  84. {
  85. int y = spl[x].fa,z = spl[y].fa;
  86. pushdown(z); pushdown(y); pushdown(x);
  87. spl[x].fa = z;
  88. if(z>0)
  89. {
  90. if(spl[z].son[0]==y) spl[z].son[0] = x;
  91. else spl[z].son[1] = x;
  92. }
  93. spl[y].son[dir^1] = spl[x].son[dir];
  94. if(spl[x].son[dir]>0) spl[spl[x].son[dir]].fa = y;
  95. spl[x].son[dir] = y; spl[y].fa = x;
  96. pushup(y); pushup(x); pushup(z);
  97. }
  98. void splaynode(int x,int dest)
  99. {
  100. while(spl[x].fa!=dest)
  101. {
  102. int y = spl[x].fa,z = spl[y].fa;
  103. if(z==dest)
  104. {
  105. if(spl[y].son[0]==x) rotate(x,1);
  106. else rotate(x,0);
  107. }
  108. else if(spl[z].son[0]==y)
  109. {
  110. if(spl[y].son[0]==x) {rotate(y,1); rotate(x,1);}
  111. else {rotate(x,0); rotate(x,1);}
  112. }
  113. else
  114. {
  115. if(spl[y].son[0]==x) {rotate(x,1); rotate(x,0);}
  116. else {rotate(y,0); rotate(x,0);}
  117. }
  118. }
  119. if(dest==0) rtn = x;
  120. }
  121. int ranktopos(int th)
  122. {
  123. int pos = rtn;
  124. while(pos)
  125. {
  126. pushdown(pos);
  127. if(th<=spl[spl[pos].son[0]].sz) pos = spl[pos].son[0];
  128. else if(th==spl[spl[pos].son[0]].sz+1) {splaynode(pos,0); return pos;}
  129. else {th -= spl[spl[pos].son[0]].sz+1; pos = spl[pos].son[1];}
  130. }
  131. return 0;
  132. }
  133. void build(int lb,int rb,int cfa)
  134. {
  135. if(lb>rb) return;
  136. int mid = (lb+rb)>>1; int pos = newnode(); id[mid] = pos;
  137. spl[pos].val = spl[pos].sum[0] = spl[pos].sum[1] = spl[pos].sum[2] = spl[pos].sum[3] = a[mid];
  138. spl[pos].fa = id[cfa];
  139. if(cfa>mid) spl[id[cfa]].son[0] = pos;
  140. else spl[id[cfa]].son[1] = pos;
  141. if(lb==rb) {spl[pos].sz = 1; return;}
  142. build(lb,mid-1,mid); build(mid+1,rb,mid);
  143. pushup(pos);
  144. }
  145. void inserttree(int x,int tot)
  146. {
  147. int posx = ranktopos(x),posy = ranktopos(x+1);
  148. splaynode(posx,0); splaynode(posy,posx);
  149. int mid = (1+tot)>>1; int pos = id[mid];
  150. spl[posy].son[0] = pos; spl[pos].fa = posy;
  151. pushup(posy); pushup(posx);
  152. }
  153. void deletenode(int pos)
  154. {
  155. if(spl[pos].son[0]) deletenode(spl[pos].son[0]);
  156. if(spl[pos].son[1]) deletenode(spl[pos].son[1]);
  157. tp++; ids[tp] = pos;
  158. spl[pos].clear();
  159. }
  160. void deletetree(int lb,int rb)
  161. {
  162. int posl = ranktopos(lb-1),posr = ranktopos(rb+1);
  163. splaynode(posl,0); splaynode(posr,posl);
  164. int pos = spl[posr].son[0];
  165. deletenode(pos);
  166. spl[posr].son[0] = 0;
  167. pushup(posr); pushup(posl);
  168. }
  169. void cover(int lb,int rb,int val)
  170. {
  171. int posl = ranktopos(lb-1),posr = ranktopos(rb+1);
  172. splaynode(posl,0); splaynode(posr,posl);
  173. int pos = spl[posr].son[0];
  174. spl[pos].tag = val; spl[pos].val = val;
  175. spl[pos].sum[3] = val*spl[pos].sz;
  176. spl[pos].sum[1] = spl[pos].sum[2] = spl[pos].sum[0] = val>0 ? val*spl[pos].sz : val;
  177. pushup(posr); pushup(posl);
  178. }
  179. void revint(int lb,int rb)
  180. {
  181. int posl = ranktopos(lb-1),posr = ranktopos(rb+1);
  182. splaynode(posl,0); splaynode(posr,posl);
  183. int pos = spl[posr].son[0];
  184. spl[pos].rev ^= 1; swap(spl[pos].son[0],spl[pos].son[1]); swap(spl[pos].sum[1],spl[pos].sum[2]);
  185. pushup(posr); pushup(posl);
  186. }
  187. int querysum(int lb,int rb)
  188. {
  189. if(rb-lb<0) return 0;
  190. int posl = ranktopos(lb-1),posr = ranktopos(rb+1);
  191. splaynode(posl,0); splaynode(posr,posl);
  192. int pos = spl[posr].son[0];
  193. return spl[pos].sum[3];
  194. }
  195. int maxsum()
  196. {
  197. return spl[rtn].sum[0];
  198. }
  199. int main()
  200. {
  201. scanf("%d%d",&n,&q);
  202. for(int i=2; i<=n+1; i++) scanf("%d",&a[i]);
  203. a[1] = a[n+2] = -INF;
  204. build(1,n+2,0); rtn = id[(n+3)>>1];
  205. memset(id,0,sizeof(id));
  206. for(int i=1; i<=q; i++)
  207. {
  208. scanf("%s",opt);
  209. if(opt[0]=='I')
  210. {
  211. int x,tot; scanf("%d%d",&x,&tot);
  212. for(int j=1; j<=tot; j++) {scanf("%d",&a[j]); id[j] = 0;}
  213. build(1,tot,0);
  214. inserttree(x+1,tot);
  215. }
  216. else if(opt[0]=='D')
  217. {
  218. int x,tot; scanf("%d%d",&x,&tot);
  219. deletetree(x+1,x+tot);
  220. }
  221. else if(opt[0]=='M' && opt[2]=='K')
  222. {
  223. int x,tot,y; scanf("%d%d%d",&x,&tot,&y);
  224. cover(x+1,x+tot,y);
  225. }
  226. else if(opt[0]=='R')
  227. {
  228. int x,tot; scanf("%d%d",&x,&tot);
  229. revint(x+1,x+tot);
  230. }
  231. else if(opt[0]=='G')
  232. {
  233. int x,tot; scanf("%d%d",&x,&tot);
  234. printf("%d\n",querysum(x+1,x+tot));
  235. }
  236. else if(opt[0]=='M' && opt[2]=='X')
  237. {
  238. printf("%d\n",maxsum());
  239. }
  240. }
  241. return 0;
  242. }

BZOJ 1500 Luogu P2042 [NOI2005] 维护数列 (Splay)的更多相关文章

  1. 洛谷 P2042 [NOI2005]维护数列-Splay(插入 删除 修改 翻转 求和 最大的子序列)

    因为要讲座,随便写一下,等讲完有时间好好写一篇splay的博客. 先直接上题目然后贴代码,具体讲解都写代码里了. 参考的博客等的链接都贴代码里了,有空再好好写. P2042 [NOI2005]维护数列 ...

  2. P2042 [NOI2005]维护数列 && Splay区间操作(四)

    到这里 \(A\) 了这题, \(Splay\) 就能算入好门了吧. 今天是个特殊的日子, \(NOI\) 出成绩, 大佬 \(Cu\) 不敢相信这一切这么快, 一下子机房就只剩我和 \(zrs\) ...

  3. P2042 [NOI2005]维护数列[splay或非旋treap·毒瘤题]

    P2042 [NOI2005]维护数列 数列区间和,最大子列和(必须不为空),支持翻转.修改值.插入删除. 练码力的题,很毒瘤.个人因为太菜了,对splay极其生疏,犯了大量错误,在此记录,望以后一定 ...

  4. Luogu P2042 [NOI2005]维护数列(平衡树)

    P2042 [NOI2005]维护数列 题意 题目描述 请写一个程序,要求维护一个数列,支持以下\(6\)种操作:(请注意,格式栏中的下划线'_'表示实际输入文件中的空格) 输入输出格式 输入格式: ...

  5. Luogu P2042 [NOI2005]维护数列

    题目描述 请写一个程序,要求维护一个数列,支持以下 6 种操作:(请注意,格式栏 中的下划线' _ '表示实际输入文件中的空格) 输入输出格式 输入格式: 输入文件的第 1 行包含两个数 N 和 M, ...

  6. NOI2005 维护数列(splay)

    学了半天平衡树,选择了一道题来写一写,发现题目是裸的splay模板,但是还是写不好,这个的精髓之处在于在数列的某一个位置加入一个数列,类似于treap里面的merge,然后还学到了题解里面的的回收空间 ...

  7. 洛谷.2042.[NOI2005]维护数列(Splay)

    题目链接 2017.12.24 第一次写: 时间: 2316ms (1268ms) 空间: 19.42MB (19.5MB)(O2) 注:洛谷测的时间浮动比较大 /* 插入一段数:将这些数先单独建一棵 ...

  8. 洛谷P2042 [NOI2005]维护数列

    #include<cstdio> #include<cstdlib> #include<algorithm> #include<cstring> #in ...

  9. P2042 [NOI2005]维护数列

    思路 超级恶心的pushdown 昏天黑地的调 让我想起了我那前几个月的线段树2 错误 这恶心的一道题终于过了 太多错误,简直说不过来 pushup pushdown 主要就是这俩不太清晰,乱push ...

随机推荐

  1. IntelliJ IDEA 问题总结之二(待补充) —— 快捷键、主题样式、导出jar、sqlite

    随着对idea的使用,问题越来越多,開始第二篇问题总结. 1.快捷键. 用惯了eclipse再用idea后,快捷键就是一个大问题. 并且网上idea的快捷键有非常多版本号.不知道是不是老版本号和新版本 ...

  2. Android 下使用opencv

    两种方式: 1.java API 2.Native/C++ 方式,OpenCV.mk中默认使用动态库的方式链接opencv,设置OPENCV_LIB_TYPE:=STATIC 以静态库方式调用 htt ...

  3. SqlServer还原步骤

    SqlServer还原步骤 2009-09-05 10:32:12|  分类: 数据库|字号 订阅     1 . 删除原有数据库 新建数据库  hywlxt 2. 在master 中新建存储过程 k ...

  4. C# 验证数字的正则表达式集

    验证数字的正则表达式集 博客分类: 正则 正则表达式 验证数字的正则表达式集 验证数字:^[0-9]*$ 验证n位的数字:^\d{n}$ 验证至少n位数字:^\d{n,}$ 验证m-n位的数字:^\d ...

  5. codeforces 899F Letters Removing set+树状数组

    F. Letters Removing time limit per test 2 seconds memory limit per test 256 megabytes input standard ...

  6. JAVA8与lambda表达式

    一.lambda表达式的来源 Lambda 表达式(拉姆达表达式)来源于lambda演算是Alonzo Church给出的一套图灵机等价的形式计算系统.lambda演算系统以函数和变量为基础,也可以进 ...

  7. Coursera Algorithms week1 查并集 练习测验:2 Union-find with specific canonical element

    题目原文: Add a method find() to the union-find data type so that find(i) returns the largest element in ...

  8. ride关键字

    定义变量:set variable 打印 :log 列表:create list 字符转数字型:evaluate 随机数:evaluate random.randint 日志截图:先导入screens ...

  9. prim解决最小生成树问题

    #include <iostream> #include <algorithm> #include <stdio.h> #include <math.h> ...

  10. Ubuntu16.04开启root用户,并远程登录

    Ubuntu安装完成默认是普通权限的用户,root用户需要手动开启,并且还不含opne-ssh模块 1.给root用户设置密码 #  sudo passwd root 会提示输入unix的新密码,这就 ...