SPOJ GSS系列真是有毒啊!
立志刷完,把线段树搞完!
来自lydrainbowcat线段树上的一道例题。(所以解法参考了lyd老师)
题意翻译
n 个数, q 次操作
操作0 x y
把 Ax 修改为 y
操作1 l r
询问区间 [l,r] 的最大子段和
数据规模在50000,有负数。
冷静分析
因为要维护最大子段和,那么我们可以在线段树struct中维护这么几个信息:
sum(区间和)、lmax(从左顶点出发的最大子段和)、rmax(从右顶点出发的最大子段和)、maxx(这段的最大子段和)以及常规的左端点left右端点right。
0操作还是比较容易的,是线段树的单点修改。线段树的操作基本上都是从1节点开始调入进行操作,对于单点修改来说,我们从顶向下寻找这个点的叶子节点,之后向上反,修改与这个点相关的线段的全部信息。在本题中代码长这样。
1 void change(int p,int x,int v) 2 { 3 if(t[p].left==t[p].right) 4 { 5 t[p].sum=t[p].maxx=t[p].lmax=t[p].rmax=v; 6 return; 7 } 8 int mid=(t[p].left+t[p].right)>>1; 9 if(x<=mid) change(p*2,x,v); 10 else change(p*2+1,x,v); 11 renew(p); 12 }
找到叶子节点后,修改它除左右端点的全部信息为要修改成的值v。在未找到叶子节点之前,我们可以运用二分的思想来找我们需要的节点。
另外,没有人对renew函数有疑问嘛?提前剧透一下好了。renew就是在更新非叶子节点的信息。
1 void renew(int p) 2 { 3 t[p].sum=t[p*2].sum+t[p*2+1].sum; 4 t[p].lmax=max(t[p*2].lmax,t[p*2].sum+t[p*2+1].lmax); 5 t[p].rmax=max(t[p*2+1].rmax,t[p*2+1].sum+t[p*2].rmax); 6 t[p].maxx=max(max(t[p*2].maxx,t[p*2+1].maxx),t[p*2].rmax+t[p*2+1].lmax); 7 }
sum等于左右儿子的sum和,这很好理解。
lmax和rmax是什么鬼??我觉得(大概)可以这样理解:因为线段树中非叶子节点的信息都是由它的两个儿子节点维护得到的,那么对于一个非叶子节点的从左顶点出发的最大子段和(lmax),可以看做左孩子的lmax与 和右孩子有关的lmax 比较取最大值得到。那么和右孩子有关的lmax如何求出?由于要保证这个非叶子节点lmax从左端出发的性质,那么和右孩子有关的lmax=左孩子的区间和+右孩子的lmax。
那么rmax的维护同理啦。
maxx的维护思想类似,我们需要比较三个最大值。左孩子的maxx,右孩子的maxx,在中间合并交界的maxx即左孩子的rmax+右孩子的lmax。
那么我们搞一搞毒瘤的1 操作。
这里用到了结构体函数,但是“SegmentTree a,b,c"意思是SegmentTree被我们新发明了一种变量类型(如int,longlong,等)。只不过我们通常定义结构体时往往需要多个n元组,于是就开了数组。但其实开一个也是可以的。然后这就相当于一个n元组(stl中的pair,pair是二元组)。
这部分的讲解写在代码里。
Code
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n,m; 4 int a[50085]; 5 struct SegmentTree{ 6 int left,right,lmax,rmax,sum,maxx; 7 }t[200002]; 8 void renew(int p) 9 { 10 t[p].sum=t[p*2].sum+t[p*2+1].sum; 11 t[p].lmax=max(t[p*2].lmax,t[p*2].sum+t[p*2+1].lmax); 12 t[p].rmax=max(t[p*2+1].rmax,t[p*2+1].sum+t[p*2].rmax); 13 t[p].maxx=max(max(t[p*2].maxx,t[p*2+1].maxx),t[p*2].rmax+t[p*2+1].lmax); 14 } 15 void build(int p,int l,int r) 16 { 17 t[p].left=l,t[p].right=r; 18 if(l==r) 19 { 20 t[p].sum=t[p].maxx=t[p].lmax=t[p].rmax=a[l]; 21 return; 22 } 23 int mid=(l+r)>>1; 24 build(p*2,l,mid); 25 build(p*2+1,mid+1,r); 26 renew(p);//信息在初始建树时就应开始维护 27 } 28 void change(int p,int x,int v) 29 { 30 if(t[p].left==t[p].right) 31 { 32 t[p].sum=t[p].maxx=t[p].lmax=t[p].rmax=v; 33 return; 34 } 35 int mid=(t[p].left+t[p].right)>>1; 36 if(x<=mid) change(p*2,x,v); 37 else change(p*2+1,x,v); 38 renew(p); 39 } 40 SegmentTree ask(int p,int x,int y) 41 {//开始和普通的线段树区间查询写法没有什么不同 42 if(x<=t[p].left&&y>=t[p].right) return t[p]; 43 int mid=(t[p].left+t[p].right)>>1; 44 SegmentTree a,b,c;//c是我们要最终返回的六元组 45 a.sum=a.maxx=a.lmax=a.rmax=-0x3f3f3f3f; 46 b.sum=b.maxx=b.lmax=b.rmax=-0x3f3f3f3f; 47 c.sum=0; 48 if(x<=mid) 49 {//递归实现 50 a=ask(p*2,x,y);//a记录当前节点左子树的信息 51 c.sum+=a.sum; 52 } 53 if(y>mid) 54 { 55 b=ask(p*2+1,x,y);//b记录当前节点右子树的信息 56 c.sum+=b.sum; 57 } 58 //与之前renew中的维护方法同理 59 c.maxx=max(max(a.maxx,b.maxx),a.rmax+b.lmax); 60 c.lmax=max(a.lmax,a.sum+b.lmax); 61 c.rmax=max(b.rmax,b.sum+a.rmax); 62 //处理特例 63 if(x>mid) c.lmax=max(c.lmax,b.lmax); 64 if(y<=mid) c.rmax=max(c.rmax,a.rmax); 65 return c; 66 } 67 int main() 68 { 69 scanf("%d",&n); 70 for(int i=1;i<=n;i++) 71 scanf("%d",&a[i]); 72 build(1,1,n); 73 scanf("%d",&m); 74 for(int i=1;i<=m;i++) 75 { 76 int op=0; 77 scanf("%d",&op); 78 if(op==1) 79 { 80 int x=0,y=0; 81 scanf("%d%d",&x,&y); 82 printf("%d\n",ask(1,x,y).maxx); 83 //函数返回的是一个 六元组 84 } 85 if(op==0) 86 { 87 int x=0,v=0; 88 scanf("%d%d",&x,&v); 89 change(1,x,v); 90 } 91 } 92 return 0; 93 }
最后小总结一发。
“从这道题目我们也可以看出,线段树作为一种比较通用的数据结构,能够维护各式各样的信息,前提是这些信息容易按照区间划分合并(又称满足区间可加性),我们只需要在父子传递信息和更新答案时稍作变化即可。"
--lydrainbowcat