题干:
给定长度为N的数列A,以及M条指令 (N≤500000, M≤100000),每条指令可能是以下两种之一:
“0 x y”,把 A[x] 改成 y。
“1 x y”,查询区间 [x,y] 中的最大连续子段和。
即
对于每个询问,输出一个整数表示答案。
一道模板题,线段树的区间最大子段和。
样例输入格式:
第一行:序列大小N
第二行:序列的N个数
第三行:指令询问次数M
第四行以后:每行一个指令
输入样例
4
1 2 3 4
4
1 1 3
0 3 -3
1 2 4
1 3 3
输出样例
6
4
-3
算法思路:
我们知道线段树是每个节点的数据都是由它左右子树的数据向上传递生产,那么左右子树的怎么拼接成父节点的最大子段和,由线段树的构成我们知道,父节点的区间根据mid划分成[l,mid],[mid+1,r]两个区间。那么父节点区间[l,r]的最大子段和可能产生在左子树,也可能产生在右子树,也可能左子树占一部分,右子树占一部分(一定要注意,子段是连续的,所以左子树的右区间正好接右子树的左区间)。
看下图:
我们发现:
需要对于所有节点,还要维护它们以左端点往右的最大子段和、右端点往左的最大子段和。
我们先考虑节点以左端点往右的最大子段和
我们分 2 种情况讨论,如下图:
节点以左端点往右的最大子段和:是上述两种情况取最大值即t[num].dl=max(t[num * 2].dl,t[num * 2].sum+t[num * 2+1].dl)。
我们再考虑节点以右端点往左的最大子段和
我们分 2 种情况讨论,如下图:
右端点往左的最大子段和:即t[num].dr=max(t[num * 2+1].dr,t[num * 2+1.sum+t[num * 2].dr)。
综上:在线段树上的每个节点上,除了区间端点外,再维护4个信息:区间和sum,区间最大连续子段和dat,以这个区间左边为起点的最大子段和dl,以这个区间右边为起点的最大子段和dr
线段树的整体框架不变,我们只需要完善build和change函数中,从下往上传递的信息:
t[num].sum=t[num* 2].sum+t[num* 2+1].sum;
t[num].dl=max(t[num* 2].dl,t[num* 2].sum+t[num* 2+1].dl);//2种情况
t[num].dr=max(t[num* 2+1].dr,t[num* 2+1].sum+t[num* 2].dr);//2种情况
t[num].dat=max(max(t[num* 2].dat,t[num* 2+1].dat),t[num* 2].dr+t[num* 2+1].dl);
重点强调:我们构造出以左端点往右的最大子段和dl、右端点往左的最大子段和dr,目的是计算出最大子段和跨越左子树和子树两部分。
如下图:
从这道题目我们可以看出,线段树作为一种比较通用的数据结构,能够维护各式各样的信息,前提是这些信息容易按照区间进行划分与合并(又称满足区间可加性)。我们只需要在父子传递信息更新答案时稍作变化即可。
不会线段树基本框架的请参考博文:
线段树(构建、单点修改、区间查询)
完整代码:
#include<iostream>
#include<cstdio>
using namespace std;
const int inf=-0x3fffffff;
struct tree{int l,r,sum,dat,dl,dr;}t[2000010];
int a[500010],n,m,i,x,y,z;
void ini(tree &a,int temp)
{
a.dat=a.sum=a.dl=a.dr=temp;
}
void calc(int num)
{
t[num].sum=t[num*2].sum+t[num*2+1].sum;
t[num].dl=max(t[num*2].dl,t[num*2].sum+t[num*2+1].dl);
t[num].dr=max(t[num*2+1].dr,t[num*2+1].sum+t[num*2].dr);
t[num].dat=max(max(t[num*2].dat,t[num*2+1].dat),t[num*2].dr+t[num*2+1].dl);
}
void build(int num,int l,int r)
{
t[num].l=l; t[num].r=r;
ini(t[num],a[l]);
if(l==r)return;
int mid=(l+r)>>1;
build(num*2,l,mid);
build(num*2+1,mid+1,r);
calc(num);
}
tree ask(int num)
{
if(x<=t[num].l&&y>=t[num].r) return t[num];//完全覆盖,是查询区间完全包含当前节点区间
int mid=(t[num].l+t[num].r)>>1;
tree a,b,c;
ini(a,inf); ini(b,inf);
c.sum=0;
if(x<=mid)
{
a=ask(num*2);
c.sum+=a.sum;
}
if(y>mid)
{
b=ask(num*2+1);
c.sum+=b.sum;
}
c.dat=max(max(a.dat,b.dat),a.dr+b.dl);
c.dl=max(a.dl,b.dl+a.sum);
if(x>mid) c.dl=max(c.dl,b.dl);
c.dr=max(b.dr,b.sum+a.dr);
if(y<=mid) c.dr=max(c.dr,a.dr);
return c;
}
void change(int num)
{
if(t[num].l==t[num].r)
{ini(t[num],y); return;}
int mid=(t[num].l+t[num].r)>>1;
if(x<=mid)change(num*2);else change(num*2+1);
calc(num);
}
int main()
{
cin>>n;
for(i=1;i<=n;i++)scanf("%d",&a[i]);
cin>>m;
build(1,1,n);
for(i=0;i<m;i++)
{
scanf("%d%d%d",&z,&x,&y);
if(z==1) {
if (x>y) swap(x,y);
printf("%d\n",ask(1).dat);
} else change(1);
}
return 0;
}