手动博客搬家: 本文发表于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, 我好菜啊
  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)

#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;
}

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. UI层自动化测试框架(一)-简介和环境搭建

    http://blog.csdn.net/ToBeTheEnder/article/details/52302777

  2. 编程算法 - 和为s的连续正整数序列 代码(C)

    和为s的连续正整数序列 代码(C) 本文地址: http://blog.csdn.net/caroline_wendy 题目: 输入一个正数s, 打印出全部和为s的连续正数序列(至少含有两个数). 起 ...

  3. Codeforces Round #330 (Div. 2) D. Max and Bike 二分

    D. Max and Bike For months Maxim has been coming to work on his favorite bicycle. And quite recently ...

  4. P1155 双栈排序(二分图染色)

    P1155 双栈排序(二分图染色) 题目描述 Tom最近在研究一个有趣的排序问题.如图所示,通过2个栈S1和S2,Tom希望借助以下4种操作实现将输入序列升序排序. 操作a 如果输入序列不为空,将第一 ...

  5. [Contest Hunter#17-C] 舞动的夜晚

    [题目链接] http://contest-hunter.org:83/contest/CH%20Round%20%2317/%E8%88%9E%E5%8A%A8%E7%9A%84%E5%A4%9C% ...

  6. selenium3 + python - table定位

    前言 在web页面中经常会遇到table表格,特别是后台操作页面比较常见.本篇详细讲解table表格如何定位. 一.认识table 1.首先看下table长什么样,如下图,这种网状表格的都是table ...

  7. Linux安装java jdk、mysql、tomcat

    安装javajdk 1.8 检查是否安装 rpm -qa | grep jdk rpm方式安装 下载java1.8 jdk http://download.oracle.com/otn-pub/jav ...

  8. Serializable资料整理

    1. 序列化 简单的说就是为了保存 内存中各种对象的状态(是实例变量,不是方法),并且可以把保存的对象读取出来. 虽然保存 object states的方法很多,但是Java提供了一种保存对象状态的机 ...

  9. jquery对象与DOM对象的转化(简化版):

    1:DOM对象 var apple = document.getElementById('apple'); 2:jquery对象 var _apple = $('#apple'); 3:DOM对象=& ...

  10. 努比亚(nubia) M2青春版 NX573J 解锁BootLoader 并进入临时recovery ROOT

    努比亚(nubia) M2青春版 NX573J 解锁BootLoader 并进入临时recovery ROOT 工具下载链接:https://pan.baidu.com/s/1NfRTdXtdAZRi ...