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

手动博客搬家: 本文发表于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]\)表示这个区间内(不限端点)的最大子段和。
比如:序列是

a: 1 -4 -2 9 -5 -7 -999 666 -999 3 0
sum[3]=1+(-4)+(-2)+8+(-5)+(-7)+(-999)+666+(-999)+3+0=-1338
sum[2]=3+0=3
sum[1]=1+(-4)+(-2)+9=4
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, 我好菜啊

  5. 建树时没有分清原数组中的下标和\(splay\)中的编号。
    详见代码。build函数中的mid是原数组,pos是节点编号,而cfa,是父亲节点在原数组中的编号。(有点乱。。)
  6. 在\(REVERSE\)操作之后没有交换\(sum[1]\)和\(sum[2]\)并\(pushup\).
    由于我们维护的是最大子段和,如果左右子节点被交换,那么\(sum[1]\)和\(sum[2]\)也随之交换。(可以认为节点的加法,即区间合并,不满足交换律)因此在\(REVERSE\)打标记的同时应当交换两个儿子以及该节点的\(sum[1]\)和\(sum[2]\), 并\(pushup\).同时,在pushdown时如果有\(reverse\)标记,也要交换当前节点的\(sum[1]\)和\(sum[2]\)
  7. 为了偷懒减少代码长度,\(sum[0]\)的合并少考虑了一种情况。(原地爆炸...以后再也不偷懒了呜呜呜)

好吧再多也没得说了,反正这道题尽管很毒瘤,但也是练习Splay的一道经典码农题,以后一定一定要抽空多写几遍!
怎么跑得这么慢啊...luogu不开O2要排后100了,bzoj开O2, 2137人AC我排1300多呜呜呜

代码实现
(luogu: 4399 ms without O2; bzoj: 5912 ms)

#include<cstdio>
#include<algorithm>
#include<cstring>
#define llong long long
using namespace std;const int SZ = 5e5;
const int N = 4e6;
const int INF = 6e8;
struct SplayNode
{int fa,son[2],tag,sum[4],sz,val;bool rev;SplayNode() {fa = son[0] = son[1] = rev = val = sz = 0; tag = sum[0] = sum[1] = sum[2] = sum[3] = -INF;}void clear() {fa = son[0] = son[1] = rev = val = sz = 0; tag = sum[0] = sum[1] = sum[2] = sum[3] = -INF;}
} spl[SZ+4],tmp[SZ+4];
int ids[N+4];
int id[SZ+4];
int a[SZ+4];
char opt[14];
int n,q,siz,rtn,tp;int newnode()
{if(tp>0) {int ret = ids[tp]; ids[tp] = 0; tp--; return ret;}else {siz++; return siz;}
}void pushup(int pos) //这里有简化很多的写法,推荐看洛谷题解
{if(pos==0) return;int ls = spl[pos].son[0],rs = spl[pos].son[1];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;}if(ls==0 && rs!=0){spl[pos].sz = spl[rs].sz+1;spl[pos].sum[3] = spl[rs].sum[3]+spl[pos].val;spl[pos].sum[2] = max(spl[pos].sum[3],spl[rs].sum[2]);spl[pos].sum[1] = max(spl[pos].sum[3],max(spl[pos].val,spl[pos].val+spl[rs].sum[1]));spl[pos].sum[0] = max(max(spl[pos].sum[1],spl[pos].sum[2]),spl[rs].sum[0]);return;}if(ls!=0 && rs==0){spl[pos].sz = spl[ls].sz+1;spl[pos].sum[3] = spl[ls].sum[3]+spl[pos].val;spl[pos].sum[2] = max(spl[pos].sum[3],max(spl[pos].val,spl[pos].val+spl[ls].sum[2]));spl[pos].sum[1] = max(spl[pos].sum[3],spl[ls].sum[1]);spl[pos].sum[0] = max(max(spl[pos].sum[1],spl[pos].sum[2]),spl[ls].sum[0]);return;}spl[pos].sz = spl[ls].sz+spl[rs].sz+1;spl[pos].sum[3] = spl[ls].sum[3]+spl[pos].val+spl[rs].sum[3];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));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));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])));
}void pushdown(int pos)
{if(pos==0) return;int ls = spl[pos].son[0],rs = spl[pos].son[1];if(ls==0 && rs==0) {spl[pos].tag = -INF; spl[pos].rev = 0; return;}if(spl[pos].tag>-INF){if(ls!=0){spl[ls].tag = spl[pos].tag; spl[ls].val = spl[ls].tag;spl[ls].sum[3] = spl[ls].tag*spl[ls].sz;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;}if(rs!=0){spl[rs].tag = spl[pos].tag; spl[rs].val = spl[rs].tag;spl[rs].sum[3] = spl[rs].tag*spl[rs].sz;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;}spl[pos].tag = -INF;}if(spl[pos].rev==true){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]);}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]);}spl[pos].rev = 0;}
}void rotate(int x,bool dir)
{int y = spl[x].fa,z = spl[y].fa;pushdown(z); pushdown(y); pushdown(x);spl[x].fa = z;if(z>0){if(spl[z].son[0]==y) spl[z].son[0] = x;else spl[z].son[1] = x;}spl[y].son[dir^1] = spl[x].son[dir];if(spl[x].son[dir]>0) spl[spl[x].son[dir]].fa = y;spl[x].son[dir] = y; spl[y].fa = x;pushup(y); pushup(x); pushup(z);
}void splaynode(int x,int dest)
{while(spl[x].fa!=dest){int y = spl[x].fa,z = spl[y].fa;if(z==dest){if(spl[y].son[0]==x) rotate(x,1);else rotate(x,0);}else if(spl[z].son[0]==y){if(spl[y].son[0]==x) {rotate(y,1); rotate(x,1);}else {rotate(x,0); rotate(x,1);}}else{if(spl[y].son[0]==x) {rotate(x,1); rotate(x,0);}else {rotate(y,0); rotate(x,0);}}}if(dest==0) rtn = x;
}int ranktopos(int th)
{int pos = rtn;while(pos){pushdown(pos);if(th<=spl[spl[pos].son[0]].sz) pos = spl[pos].son[0];else if(th==spl[spl[pos].son[0]].sz+1) {splaynode(pos,0); return pos;}else {th -= spl[spl[pos].son[0]].sz+1; pos = spl[pos].son[1];}}return 0;
}void build(int lb,int rb,int cfa)
{if(lb>rb) return;int mid = (lb+rb)>>1; int pos = newnode(); id[mid] = pos;spl[pos].val = spl[pos].sum[0] = spl[pos].sum[1] = spl[pos].sum[2] = spl[pos].sum[3] = a[mid];spl[pos].fa = id[cfa];if(cfa>mid) spl[id[cfa]].son[0] = pos;else spl[id[cfa]].son[1] = pos;if(lb==rb) {spl[pos].sz = 1; return;}build(lb,mid-1,mid); build(mid+1,rb,mid);pushup(pos);
}void inserttree(int x,int tot)
{int posx = ranktopos(x),posy = ranktopos(x+1);splaynode(posx,0); splaynode(posy,posx);int mid = (1+tot)>>1; int pos = id[mid];spl[posy].son[0] = pos; spl[pos].fa = posy;pushup(posy); pushup(posx);
}void deletenode(int pos)
{if(spl[pos].son[0]) deletenode(spl[pos].son[0]);if(spl[pos].son[1]) deletenode(spl[pos].son[1]);tp++; ids[tp] = pos;spl[pos].clear();
}void deletetree(int lb,int rb)
{int posl = ranktopos(lb-1),posr = ranktopos(rb+1);splaynode(posl,0); splaynode(posr,posl);int pos = spl[posr].son[0];deletenode(pos);spl[posr].son[0] = 0;pushup(posr); pushup(posl);
}void cover(int lb,int rb,int val)
{int posl = ranktopos(lb-1),posr = ranktopos(rb+1);splaynode(posl,0); splaynode(posr,posl);int pos = spl[posr].son[0];spl[pos].tag = val; spl[pos].val = val;spl[pos].sum[3] = val*spl[pos].sz;spl[pos].sum[1] = spl[pos].sum[2] = spl[pos].sum[0] = val>0 ? val*spl[pos].sz : val;pushup(posr); pushup(posl);
}void revint(int lb,int rb)
{int posl = ranktopos(lb-1),posr = ranktopos(rb+1);splaynode(posl,0); splaynode(posr,posl);int pos = spl[posr].son[0];spl[pos].rev ^= 1; swap(spl[pos].son[0],spl[pos].son[1]); swap(spl[pos].sum[1],spl[pos].sum[2]);pushup(posr); pushup(posl);
}int querysum(int lb,int rb)
{if(rb-lb<0) return 0;int posl = ranktopos(lb-1),posr = ranktopos(rb+1);splaynode(posl,0); splaynode(posr,posl);int pos = spl[posr].son[0];return spl[pos].sum[3];
}int maxsum()
{return spl[rtn].sum[0];
}int main()
{scanf("%d%d",&n,&q);for(int i=2; i<=n+1; i++) scanf("%d",&a[i]);a[1] = a[n+2] = -INF;build(1,n+2,0); rtn = id[(n+3)>>1];memset(id,0,sizeof(id));for(int i=1; i<=q; i++){scanf("%s",opt);if(opt[0]=='I'){int x,tot; scanf("%d%d",&x,&tot);for(int j=1; j<=tot; j++) {scanf("%d",&a[j]); id[j] = 0;}build(1,tot,0);inserttree(x+1,tot);}else if(opt[0]=='D'){int x,tot; scanf("%d%d",&x,&tot);deletetree(x+1,x+tot);}else if(opt[0]=='M' && opt[2]=='K'){int x,tot,y; scanf("%d%d%d",&x,&tot,&y);cover(x+1,x+tot,y);}else if(opt[0]=='R'){int x,tot; scanf("%d%d",&x,&tot);revint(x+1,x+tot);}else if(opt[0]=='G'){int x,tot; scanf("%d%d",&x,&tot);printf("%d\n",querysum(x+1,x+tot));}else if(opt[0]=='M' && opt[2]=='X'){printf("%d\n",maxsum());}}return 0;
}

发表于 2019-01-22 19:41 suncongbo 阅读(...) 评论(...) 编辑 收藏

刷新评论刷新页面返回顶部

BZOJ 1500 Luogu P2042 [NOI2005] 维护数列 (Splay)相关推荐

  1. 数据结构(Splay平衡树):COGS 339. [NOI2005] 维护数列

    339. [NOI2005] 维护数列 时间限制:3 s   内存限制:256 MB [问题描述] 请写一个程序,要求维护一个数列,支持以下 6 种操作:(请注意,格式栏 中的下划线' _ '表示实际 ...

  2. [BZOJ1500][NOI2005]维修数列(splay)

    1500: [NOI2005]维修数列 Time Limit: 10 Sec  Memory Limit: 64 MB Submit: 16266  Solved: 5410 [Submit][Sta ...

  3. 【BZOJ1500】[NOI2005]维修数列 Splay

    [BZOJ1500][NOI2005]维修数列 Description Input 输入的第1 行包含两个数N 和M(M ≤20 000),N 表示初始时数列中数的个数,M表示要进行的操作数目. 第2 ...

  4. [NOI2005]维护数列 恶心到毁天灭地的splay

    传送门 debug到死2333. 虽然说是splay维护序列模板,作为蒟蒻的我还是GG %%%考场A的dalao Orz  Orz. 其实不开long long也行,inf开成0x3f3f3f3f也可 ...

  5. 1500: [NOI2005]维修数列 (Splay)

    1W1A(inf开太大) #include<algorithm> #include<iostream> #include<cstring> #include< ...

  6. BZOJ1500 [NOI2005]维修数列(Splay tree)

    [Submit][Status][Discuss] Description 请写一个程序,要求维护一个数列,支持以下 6 种操作: 请注意,格式栏 中的下划线' _ '表示实际输入文件中的空格 Inp ...

  7. [NOI2005]维护数列

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

  8. [luogu2042] [NOI2005]维护数列

    前言 写写比较麻烦的这题 题目相关 题目大意 写一个大数据结构 数据范围 20000 题目链接 前置 先过模板题,比如会个非旋treap,写一下,通过[luogu3369][模板]普通平衡树 poss ...

  9. [BZOJ 1500] [NOI2005] 维修数列

    题目链接:BZOJ - 1500 题目分析 我要先说一下,这道题我写了一晚上,然后Debug了一整个白天..........再一次被自己的蒟蒻程度震惊= = 这道题是传说中的Splay维护数列的Bos ...

最新文章

  1. 文件时间信息在测试中的应用
  2. ID3 Algorithm for Decision Trees
  3. 这年头居然连MSDN Library都靠不住呀
  4. ibatis中使用like模糊查询
  5. 2、Python连接Mysql数据库。
  6. 6.824 MapReduce lab1 2020(一)
  7. 【转】你不能访问此共享文件夹,因为你组织的安全策略阻止未经身份验证的来宾访问
  8. AVAudioSession
  9. 省选+NOI 第八部分 数论
  10. oracle 052 题库更新,OCP题库升级,新版052考试题及答案整理-18
  11. 【IDEA】IDEA 下 如何 jstack 线程状态
  12. WPF开发为按钮提供添加,删除和重新排列ListBox内容的功能
  13. 关于标题的多HTML定制
  14. Atitit 知识与数据 信息 加工方法总结 目录 1.1. 信息加工是指通过判别、筛选、分类、排序、分析和研究等一系列过程 1 1.2. 首先通过聚类信息 专题化 分组聚合 1 1.3. 多种聚
  15. 微信小程序自定义Modal弹框
  16. nginx跨域配置、使用,docker部署nginx全流程(实测简单完整无坑)
  17. 永恒之蓝-MS17010 CVE-2017-0146
  18. java 箭头符号_箭头符号大全
  19. 开发钉钉小程序(后台)心得
  20. RecyclerView EditText数据混乱解决

热门文章

  1. linux卸载python3.6,当python3.6位于/usr/local/bin/python3.6时,如何在ubuntu上卸载它
  2. Sorm进阶(1):storm实现github提交数监控看板
  3. 常用WebService一览表
  4. js中的可变参数arguments与json
  5. 想成为嵌入式程序员应知道的0x10个基本问题
  6. laydate.render报错:日期格式不合法
  7. Spring IOC流程源码分析
  8. 操作系统(三十二)内存的基础常识
  9. C语言再学习 -- 关键字sizeof与strlen
  10. android开发 Gradle多渠道打包以及集成360加固