题目描述
给定长度为N的数列A,以及M条指令,每条指令可能是以下两种之一:
1、“1 x y”,查询区间 [x,y] 中的最大连续子段和,即 maxx≤l≤r≤y{∑ri=lA[i]}。
2、“2 x y”,把 A[x] 改成 y。
对于每个查询指令,输出一个整数表示答案。
输入格式
第一行两个整数N,M。
第二行N个整数A[i]。
接下来M行每行3个整数k,x,y,k=1表示查询(此时如果x>y,请交换x,y),k=2表示修改。
输出格式
对于每个查询指令输出一个整数表示答案。
每个答案占一行。
数据范围
N≤500000,M≤100000
输入样例:
5 3
1 2 -3 4 5
1 2 3
2 2 -1
1 3 2
输出样例:
2
-1
题目分析
这个题每次查询的是[l,r]中的最大子段和。那么每一段中我们也就要维护一个区间的最大子段和tmax。
但是我们可以发现:只依靠一个区间的左区间的tmax和右区间的tmax并不能够求出这个区间的tmax。
一个区间的最大子段和有三种可能的情况,1.这个区间的最大子段和完全在该区间的左区间内。2.这个区间的最大子段和完全在该区间的右区间内。3.这个区间的最大子段和占了左区间的后半部分和右区间的前半部分。因此我们还需要添加两个辅助信息lmax(该区间的最大前缀和)和rmax(这个区间的最大后缀和)。
那么一个区间的最大前缀和和最大后缀和怎么算呢?以前缀和为例:这个区间的最大前缀和包含两种情况:1.这个区间的最大前缀和小于一半,那么等于这个区间的左区间的最大前缀和。2.这个区间的最大前缀和大于一半,那么等于这个区间左区间的和+右区间的最大前缀和。
后缀和与前缀和求法相同,等于这个区间的右区间的最大后缀和或者这个区间右区间的和+左区间的最大后缀和。
那么,我们还需要添加一个辅助信息sum(记录这个区间的和)而它的求法即为左右两个区间的sum相加即可。
这样,所有的辅助信息及其求法就都确定了。用线段树求解即可。
tmax=(左右区间的最大子段和)以及(左区间的最大后缀和+右区间的最大前缀和)的最大值
lmax=(左区间的最大前缀和)以及(左区间的和+右区间的最大前缀和)的最大值
rmax=(右区间的最大后缀和)以及(右区间的和+左区间的最大后缀和)的最大值
sum=左右区间的sum的和
代码如下
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=5e5+5;
struct Node{
int l,r;
int sum,lmax,rmax,tmax; //几个辅助信息
}tr[N*4];
int w[N];
void pushup(Node &u,Node &l,Node &r) //通过子段的信息求出u段的全部辅助信息
{
u.sum=l.sum+r.sum; //这些的求法前面都讲了
u.lmax=max(l.lmax,l.sum+r.lmax);
u.rmax=max(r.rmax,r.sum+l.rmax);
u.tmax=max(max(l.tmax,r.tmax),l.rmax+r.lmax);
}
void pushup(int u) //包装一下,后面调用时写的舒服些
{
pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void build(int u,int l,int r) //建树
{
if(l==r) tr[u]={l,r,w[r],w[r],w[r],w[r]};//l==r,u段中只有一个数,所以所有的辅助信息的值都是w[r]
else
{
tr[u]={l,r};
int mid=l+r>>1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);
pushup(u);
}
}
void modify(int u,int x,int v) //把x位置的值修改为v
{
if(tr[u].l==x&&tr[u].r==x) tr[u]={x,x,v,v,v,v}; //当u段只有x一个数时,所有辅助信息都为v
else
{ //查看x位于那一段中,则修改哪一段
int mid=tr[u].l+tr[u].r>>1;
if(x<=mid) modify(u<<1,x,v);
else modify(u<<1|1,x,v);
pushup(u); //修改后要更新u段的辅助信息
}
}
Node query(int u,int l,int r) //查询[l,r]段内的信息
{
if(l<=tr[u].l&&r>=tr[u].r) return tr[u]; //如果u段在[l,r]内直接返回
else
{
int mid=tr[u].l+tr[u].r>>1;
if(mid>=r) return query(u<<1,l,r); //mid>=r,说明u段的左区间部分在[l,r]内
else if(mid<l) return query(u<<1|1,l,r);//mid<l,说明u段的右区间部分在[l,r]内
else //否则说明u段的左右区间都有部分在[l,r]内
{
Node left=query(u<<1,l,r);
Node right=query(u<<1|1,l,r);
Node res;
pushup(res,left,right);
return res;
}
}
}
int main()
{
int n,m;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&w[i]);
build(1,1,n); //建树,范围为1-n
while(m--)
{
int k,x,y;
scanf("%d %d %d",&k,&x,&y);
if(k==1)
{
if(x>y) swap(x,y); //保证y>x
printf("%d\n",query(1,x,y).tmax); //直接查询[l,r]的tmax
}
else modify(1,x,y); //修改信息
}
return 0;
}