你能回答这些问题吗(线段树:求区间最大连续子段和)

给定长度为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

思路:用线段树维护区间和的同时增加lmax和rmax变量维护区间最大前缀子段和以及区间最大后缀子段和,然后利用这些信息的关系可以维护区间最大连续子段和maxsum,每个连续子段都可以通过左区间和,右区间和和lmax,rmax相结合的部分构造出来:

区间前缀的最大连续子段和=max(左子区间和+右子区间的前缀的的最大连续子段)

区间后缀的最大连续子段和=max(右子区间和+左子区间的后缀的的最大连续子段)

用maxsum维护这些信息的max值即可,即:

区间最大连续子段和=最大的(左子区间的后缀最大子段和+右子区间前缀的最大子段和)

完整代码:

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int maxn=5e5+5,inf=0x3f3f3f3f;

struct tree
{
    int l,r,lmax,rmax,sum,maxsum;
}tr[maxn<<2];

int n,m,a[maxn];

void pushup(int p)//回溯时,通过子结点信息维护父结点信息
{
    tr[p].sum=tr[p<<1].sum+tr[p<<1|1].sum;//父结点区间和等于其左右子区间和的和
    tr[p].lmax=max(tr[p<<1].lmax,tr[p<<1].sum+tr[p<<1|1].lmax);//区间前缀的最大连续子段和=所有左子区间和和右子区间的前缀的的最大连续子段和构造出来的所有子段和的最大值
    tr[p].rmax=max(tr[p<<1|1].rmax,tr[p<<1|1].sum+tr[p<<1].rmax);//区间后缀的最大连续子段和=所有右子区间和和左子区间的后缀的的最大连续子段和构造出来的所有子段和的最大值
    tr[p].maxsum=max(max(tr[p<<1].maxsum,tr[p<<1|1].maxsum),tr[p<<1].rmax+tr[p<<1|1].lmax);
}

void build(int p,int l,int r)
{
    tr[p].l=l;
    tr[p].r=r;
    if(l==r){
        tr[p]={l,r,a[l],a[l],a[l],a[l]};
        return;
    }
    int mid=l+r>>1;
    build(p<<1,l,mid);
    build(p<<1|1,mid+1,r);
    pushup(p);
}

void update(int p,int k,int v)
{
    if(tr[p].l==tr[p].r){
        tr[p].sum=v;
        tr[p].lmax=v;
        tr[p].rmax=v;
        tr[p].maxsum=v;
        return;
    }
    int mid=tr[p].l+tr[p].r>>1;
    if(k<=mid) update(p<<1,k,v);
    else update(p<<1|1,k,v);
    pushup(p);
}

tree query(int p,int l,int r)
{
    if(tr[p].l>=l&&tr[p].r<=r){
        return tr[p];
    }
    int mid=tr[p].l+tr[p].r>>1;
    tree a,b,c;
    a.sum=b.sum=-inf;
    a.lmax=b.lmax=-inf;
    a.rmax=b.rmax=-inf;
    a.maxsum=b.maxsum=-inf;
    c.sum=0;
    if(l<=mid){
        a=query(p<<1,l,r);
        c.sum+=a.sum;
    }
    if(r>mid){
        b=query(p<<1|1,l,r);
        c.sum+=b.sum;
    }
    c.maxsum=max(max(a.maxsum,b.maxsum),a.rmax+b.lmax);
    c.lmax=max(a.lmax,a.sum+b.lmax);
    if(l>mid){
        c.lmax=max(c.lmax,b.lmax);
    }
    c.rmax=max(b.rmax,b.sum+a.rmax);
    if(r<=mid){
        c.rmax=max(c.rmax,a.rmax);
    }
    return c;
}

int main()
{
    ios::sync_with_stdio();
    cin.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];
    build(1,1,n);
    while(m--){
        int op,u,v;
        cin>>op>>u>>v;
        if(op==1){
            if(u>v) swap(u,v);
            cout<<query(1,u,v).maxsum<<endl;
        }
        else{
            update(1,u,v);
        }
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值