因为要讲座,随便写一下,等讲完有时间好好写一篇splay的博客。

先直接上题目然后贴代码,具体讲解都写代码里了。

参考的博客等的链接都贴代码里了,有空再好好写。

P2042 [NOI2005]维护数列

题目描述

请写一个程序,要求维护一个数列,支持以下 6 种操作:(请注意,格式栏 中的下划线‘ _ ’表示实际输入文件中的空格)

输入输出格式

输入格式:

输入文件的第 1 行包含两个数 N 和 M,N 表示初始时数列中数的个数,M 表示要进行的操作数目。 第 2 行包含 N 个数字,描述初始时的数列。 以下 M 行,每行一条命令,格式参见问题描述中的表格

输出格式:

对于输入数据中的 GET-SUM 和 MAX-SUM 操作,向输出文件依次打印结 果,每个答案(数字)占一行。

输入输出样例

输入样例#1: 复制

  1. 9 8
  2. 2 -6 3 5 1 -5 -3 6 3
  3. GET-SUM 5 4
  4. MAX-SUM
  5. INSERT 8 3 -5 7 2
  6. DELETE 12 1
  7. MAKE-SAME 3 3 2
  8. REVERSE 3 6
  9. GET-SUM 5 4
  10. MAX-SUM
输出样例#1: 复制

  1. -1
  2. 10
  3. 1
  4. 10

说明

你可以认为在任何时刻,数列中至少有 1 个数。

输入数据一定是正确的,即指定位置的数在数列中一定存在。

50%的数据中,任何时刻数列中最多含有 30 000 个数;

100%的数据中,任何时刻数列中最多含有 500 000 个数。

100%的数据中,任何时刻数列中任何一个数字均在[-1 000, 1 000]内。

100%的数据中,M ≤20 000,插入的数字总数不超过 4 000 000 。

代码:

  1. /*
  2. https://www.luogu.org/problemnew/show/P2042
  3. https://www.luogu.org/problemnew/solution/P2042
  4. https://baijiahao.baidu.com/s?id=1613228134219334653&wfr=spider&for=pc
  5. https://www.cnblogs.com/victorique/p/8478866.html
  6. https://www.cnblogs.com/noip/archive/2013/05/31/3111169.html
  7. https://baike.baidu.com/item/%E4%BC%B8%E5%B1%95%E6%A0%91/7003945?fr=aladdin
  8. https://blog.csdn.net/changtao381/article/details/8936765
  9. https://blog.csdn.net/huzujun/article/details/81394092
  10. */
  11.  
  12. //插入 删除 修改 翻转 求和 最大的子序列
  13. #include<bits/stdc++.h>
  14. using namespace std;
  15. typedef long long ll;
  16. const int maxn=1e6+;
  17. const int inf=0x3f3f3f3f;
  18.  
  19. int n,m,rt,cnt;
  20. int a[maxn],id[maxn],fa[maxn],tree[maxn][];
  21. int sum[maxn],sz[maxn],val[maxn],mx[maxn],lx[maxn],rx[maxn];
  22. int tag[maxn],rev[maxn];
  23. //tag 是否有统一修改的标记,rev 是否有统一翻转的标记
  24.  
  25. queue<int> q;
  26.  
  27. void pushup(int x)//分治,类似线段树的区间合并,但是因为当前节点也有值,所以要加上当前节点的val
  28. {
  29. int l=tree[x][],r=tree[x][];
  30. sum[x]=sum[l]+sum[r]+val[x];
  31. sz[x]=sz[l]+sz[r]+;
  32. lx[x]=max(lx[l],sum[l]+lx[r]+val[x]);
  33. rx[x]=max(rx[r],sum[r]+rx[l]+val[x]);
  34. mx[x]=max(max(mx[l],mx[r]),rx[l]+lx[r]+val[x]);//区间最大子段和
  35. }
  36.  
  37. void pushdown(int x)
  38. {
  39. int l=tree[x][],r=tree[x][];
  40. if(tag[x]){//有统一修改的标记,翻转就没有意义了
  41. rev[x]=tag[x]=;
  42. if(l) tag[l]=,val[l]=val[x],sum[l]=sz[l]*val[x];
  43. if(r) tag[r]=,val[r]=val[x],sum[r]=sz[r]*val[x];
  44. if(val[x]>=){
  45. if(l) lx[l]=rx[l]=mx[l]=sum[l];
  46. if(r) lx[r]=rx[r]=mx[r]=sum[r];
  47. }
  48. else{
  49. if(l) lx[l]=rx[l]=,mx[l]=val[x];
  50. if(r) lx[r]=rx[r]=,mx[r]=val[x];
  51. }
  52. }
  53. if(rev[x]){
  54. rev[x]=;rev[l]^=;rev[r]^=;
  55. swap(lx[l],rx[l]);swap(lx[r],rx[r]);//注意,在翻转操作中,前后缀的最长上升子序列都反过来了,很容易错
  56. swap(tree[l][],tree[l][]);swap(tree[r][],tree[r][]);
  57. }
  58. }
  59.  
  60. void rotate(int x,int &k)
  61. {
  62. int y=fa[x],z=fa[y],l=(tree[y][]==x),r=l^;
  63. if(y==k) k=x;
  64. else tree[z][tree[z][]==y]=x;
  65. fa[tree[x][r]]=y;fa[y]=x;fa[x]=z;//改变父子关系。爸爸变儿子,爷爷变爸爸
  66. tree[y][l]=tree[x][r];tree[x][r]=y;
  67. pushup(y);pushup(x);//旋转操作,改变关系之后标记上传
  68. }
  69.  
  70. /*
  71. 伸展操作,三种状态:
  72. 1.x的爸爸y是目标状态,直接翻转x
  73. 2.x有爸爸y,有爷爷z,如果三点在一条直线上,就先翻转爸爸y,这样翻转是双旋,保持平衡(关于旋转 双旋、单旋,讲一下)
  74. 3.x有爸爸y,有爷爷z,三点不在一条直线上,直接翻转两次x就可以
  75. */
  76. void splay(int x,int &k)//伸展操作,核心操作
  77. {
  78. while(x!=k){//一直到转到目标状态
  79. int y=fa[x],z=fa[y];
  80. if(y!=k){//如果爸爸不是目标状态
  81. if((tree[z][]==y)^(tree[y][]==x)) rotate(x,k);//如果三点不在一条直线上,直接转自己
  82. else rotate(y,k);
  83. }
  84. rotate(x,k);
  85. }
  86. }
  87.  
  88. /*
  89. 查找操作,核心操作之二
  90. 区间翻转和插入以及删除的操作都需要find操作
  91. 因为维护的区间的实际编号是不连续的,所以需要查找要操作的区间对应平衡树的中序遍历的那段区间
  92. */
  93. int find(int x,int rk)//找排名第rk的
  94. {
  95. pushdown(x);//因为所有操作都是需要find,所以在这里标记下传就可以
  96. int l=tree[x][],r=tree[x][];
  97. if(sz[l]+==rk) return x;//就是二叉树的搜索操作
  98. if(sz[l] >=rk) return find(l,rk);
  99. else return find(r,rk-sz[l]-);
  100.  
  101. }
  102.  
  103. /*
  104. 这道题极限是4*10^6*log(2*10^4),2为底,二分,所以4*10^10,128MB差不多存10^8,爆内存
  105. 用时间换空间的回收冗余编号机制
  106. */
  107. void recycle(int x)//垃圾回收,节省内存,因为内存开销太大,容易爆内存,记录用过但是已经删除的节点的编号,新建节点的时候直接从队列或者栈中取出来用就可以。时间换空间
  108. {
  109. int &l=tree[x][],&r=tree[x][];
  110. if(l) recycle(l);
  111. if(r) recycle(r);//垃圾回收,一直回收到底
  112. q.push(x);
  113. fa[x]=tag[x]=rev[x]=l=r=;
  114. }
  115.  
  116. /*
  117. 核心操作之三
  118. 通过split 找到[k+1,k+tot],然后把k,k+tot+1移到根和右儿子的位置
  119. 然后返回这个右儿子的左儿子,就是要操作的区间
  120. */
  121. int split(int k,int tot)
  122. {
  123. int x=find(rt,k),y=find(rt,k+tot+);
  124. splay(x,rt);splay(y,tree[x][]);
  125. return tree[y][];
  126. }
  127.  
  128. void query(int k,int tot)//区间最大子段和
  129. {
  130. int x=split(k,tot);
  131. printf("%d\n",sum[x]);
  132. }
  133.  
  134. void modify(int k,int tot,int value)//当前数列第k个开始连续tot个统一修改为value
  135. {
  136. int x=split(k,tot),y=fa[x];
  137. val[x]=value;tag[x]=;sum[x]=sz[x]*value;
  138. if(value>=) lx[x]=rx[x]=mx[x]=sum[x];
  139. else lx[x]=rx[x]=,mx[x]=value;//最大的子段和就是一个,因为是负数
  140. pushup(y);pushup(fa[y]);//每一步的修改操作,父子关系发生变化,记录标记发生变化,所以要及时标记上传
  141. }
  142.  
  143. void rever(int k,int tot)//当前数列第k个开始的tot个数字翻转
  144. {
  145. int x=split(k,tot),y=fa[x];
  146. if(!tag[x]){
  147. rev[x]^=;
  148. swap(tree[x][],tree[x][]);
  149. swap(lx[x],rx[x]);
  150. pushup(y);pushup(fa[y]);
  151. }
  152. }
  153.  
  154. void erase(int k,int tot)//当前数列第k个数字开始连续删除tot个数字
  155. {
  156. int x=split(k,tot),y=fa[x];
  157. recycle(x);tree[y][]=;
  158. pushup(y);pushup(fa[y]);
  159. }
  160.  
  161. void build(int l,int r,int f)
  162. {
  163. int m=(l+r)>>,now=id[m],pre=id[f];
  164. if(l==r){
  165. mx[now]=sum[now]=a[l];
  166. tag[now]=rev[now]=;//这里的清零操作是必要的,因为可能是之前垃圾回收,冗余的
  167. lx[now]=rx[now]=max(a[l],);
  168. sz[now]=;
  169. }
  170. if(l<m) build(l,m-,m);
  171. if(r>m) build(m+,r,m);
  172. val[now]=a[m];fa[now]=pre;
  173. pushup(now);
  174. tree[pre][m>=f]=now;//插入右或者左区间
  175. }
  176.  
  177. void insert(int k,int tot)//当前数列第k个数字后插入tot个数字
  178. {
  179. for(int i=;i<=tot;i++){
  180. scanf("%d",&a[i]);
  181. }
  182. for(int i=;i<=tot;i++){
  183. if(!q.empty()) id[i]=q.front(),q.pop();
  184. else id[i]=++cnt;//利用队列里的冗余节点编号
  185. }
  186. build(,tot,);//将读入的tot个数建成一个平衡树
  187. int z=id[(+tot)>>];//取中点为根
  188. int x=find(rt,k+),y=find(rt,k+);//首先,根据中序遍历,找到要操作的区间的实际编号
  189. splay(x,rt);splay(y,tree[x][]);//把k+1(注意我们已经右移了一个单位)和(k+1)+1移到根和右儿子
  190. fa[z]=y;tree[y][]=z;//直接把需要插入的这个平衡树挂到右儿子的左儿子上去就好了
  191. pushup(y);pushup(x);
  192. }
  193.  
  194. //对于具体在哪里上传标记和下传标记
  195. //可以这么记,只要用了split就要重新上传标记
  196. //只有find中需要下传标记
  197. //但其实,你多传几次是没有关系的,但是少传了就不行了
  198. int main()
  199. {
  200. scanf("%d%d",&n,&m);
  201. mx[]=a[]=a[n+]=-inf;//两个虚拟节点,哨兵节点
  202. for(int i=;i<=n;i++){
  203. scanf("%d",&a[i+]);
  204. }
  205. for(int i=;i<=n+;i++){//虚拟了两个节点1和n+2,然后把需要操作区间整体右移一个单位
  206. id[i]=i;
  207. }
  208. build(,n+,);
  209. rt=(n+)>>;cnt=n+;//取最中间的为根,这样就是一个完美的平衡树
  210. int k,tot,value;char op[];
  211. for(int i=;i<=m;i++){
  212. scanf("%s",op);
  213. if(op[]!='M'||op[]!='X') scanf("%d%d",&k,&tot);
  214. if(op[]=='I') insert(k,tot);
  215. if(op[]=='D') erase(k,tot);
  216. if(op[]=='M'){
  217. if(op[]=='X') printf("%d\n",mx[rt]);
  218. else scanf("%d",&value),modify(k,tot,value);
  219. }
  220. if(op[]=='R') rever(k,tot);
  221. if(op[]=='G') query(k,tot);
  222. }
  223. return ;
  224. }

洛谷 P2042 [NOI2005]维护数列-Splay(插入 删除 修改 翻转 求和 最大的子序列)的更多相关文章

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

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

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

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

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

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

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

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

  5. BZOJ 1500 Luogu P2042 [NOI2005] 维护数列 (Splay)

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

  6. 【洛谷P2042】维护数列

    题目大意:维护一个序列,支持区间插入,区间删除,区间翻转,查询区间元素和,查询区间最大子段和操作. 题解:毒瘤题...QAQ打完这道题发现自己以前学了一个假的 Splay.. 对于区间操作,用 spl ...

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

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

  8. Luogu P2042 [NOI2005]维护数列

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

  9. NOI2005 维护数列(splay)

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

随机推荐

  1. Bootstrap简单应用——对首页进行重构

    <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8& ...

  2. Java并发编程原理与实战四十:JDK8新增LongAdder详解

    传统的原子锁AtomicLong/AtomicInt虽然也可以处理大量并发情况下的计数器,但是由于使用了自旋等待,当存在大量竞争时,会存在大量自旋等待,而导致CPU浪费,而有效计算很少,降低了计算效率 ...

  3. Presto通过RESTful接口新增Connector

    在实际使用Presto的过程中,经常会有以下的一些需求. 添加一个新的Catalog 对不再使用的Catalog希望把它删除 修改某个Catalog的参数 但在Presto中如果进行上述的修改,需要重 ...

  4. 20155330 2016-2017-2 《Java程序设计》第七周学习总结

    20155330 2016-2017-2 <Java程序设计>第七周学习总结 教材学习内容总结 学习目标 了解Lambda语法 了解方法引用 了解Fucntional与Stream API ...

  5. sklearn_PCA主成分降维

    # coding:utf-8 import pandas as pd import numpy as np from pandas import Series,DataFramefrom sklear ...

  6. oracle04--伪列

    1. 伪列 1.1. 什么是伪列 伪列是在ORACLE中的一个虚拟的列. 伪列的数据是由ORACLE进行维护和管理的,用户不能对这个列修改,只能查看. 所有的伪列要得到值必须要显式的指定. 最常用的两 ...

  7. jQuery 库的优缺点

    通用性良好,适合大多数常规网站,省去了为浏览器兼容性写封装函数的麻烦(1+版本支持IE6.7.8,2+版本支持包括IE9在内的现代浏览器). 通用性良好意味着特异性不好,所以jQuery并不适合特异性 ...

  8. 2016.5.24——Intersection of Two Linked Lists

    Intersection of Two Linked Lists 本题收获: 1.链表的输入输出 2.交叉链表:这个链表可以有交叉点,只要前一个节点的的->next相同即可. 题目:Inters ...

  9. java系统的优化

    1.tomcat.jboss.jetty的jvm内存,增大 2.数据库的优化,如MySQL的innodb_buffer_pool_size等参数,增大

  10. Go 1 Release Notes

    Go 1 Release Notes Introduction to Go 1 Changes to the language Append Close Composite literals Goro ...