线段树维护区间最大子段和

Posted wxl-ezio

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线段树维护区间最大子段和相关的知识,希望对你有一定的参考价值。

线段树:我还是很强的


简略讲解

要用线段树维护区间,我们要明确:

  • 线段树存什么东西
  • 怎么合并
  • 如果有区间修改,怎么打标记

对于区间最大子段和,我们可以记录四个值:以维护的区间左端点为起点的最大子段和,以维护的区间右端点为终点的最大子段和,在维护区间内的最大子段和 和维护区间所有元素的和

合并的话稍微麻烦一些,看代码吧:

inline void up(int p){
    tree[p].sum=tree[ls].sum+tree[rs].sum; //维护区间总和
    tree[p].ll=max(tree[ls].ll,tree[ls].sum+tree[rs].ll); 
//左端点的最大子段和可能为左儿子的左端点最大子段和,也可能为左儿子区间和 和右儿子左端点最大子段和拼起来的最大子段和
    tree[p].lr=max(tree[rs].lr,tree[rs].sum+tree[ls].lr);
//右端点最大子段和同理
    tree[p].lm=max(tree[ls].lr+tree[rs].ll,max(tree[ls].lm,tree[rs].lm));
//中间的最大子段和可能为左/右儿子中间的的最大子段和,也可能为左右儿子拼起来的和
}

然后我们的线段树那就可以维护最大子段和了。

例题

(1): SPOJ 1043

GSS1 - Can you answer these queries I

裸题,不解释,直接上代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ls p<<1
#define rs p<<1|1
#define mid ((l+r)>>1)
#define inf 0x7ffffffffLL
#define LL long long
using namespace std;
struct zzz{
    LL ll,lr,lm,sum;
}tree[200010<<2];
int a[200010];
inline void up(int p){
    tree[p].sum=tree[ls].sum+tree[rs].sum;
    tree[p].ll=max(tree[ls].ll,tree[ls].sum+tree[rs].ll);
    tree[p].lr=max(tree[rs].lr,tree[rs].sum+tree[ls].lr);
    tree[p].lm=max(tree[ls].lr+tree[rs].ll,max(tree[ls].lm,tree[rs].lm));
}
void build(int l,int r,int p){
    if(l==r){
        tree[p].sum=tree[p].ll=tree[p].lr=tree[p].lm=a[l];
        return ;
    }
    build(l,mid,ls); build(mid+1,r,rs);
    up(p);
}
zzz query(int l,int r,int p,int nl,int nr){
    zzz a={-inf,-inf,-inf,-inf},b={-inf,-inf,-inf,-inf},ans={-inf,-inf,-inf,-inf};
    if(l>=nl&&r<=nr) return tree[p];
    if(nl<=mid) a=query(l,mid,ls,nl,nr);
    if(nr>mid) b=query(mid+1,r,rs,nl,nr);
    ans.sum=a.sum+b.sum;
    ans.ll=max(a.ll,a.sum+b.ll);
    ans.lr=max(b.lr,b.sum+a.lr);
    ans.lm=max(a.lr+b.ll,max(a.lm,b.lm));
    return ans;
}
int read(){
    int k=0,f=1; char c=getchar();
    for(;c<'0'||c>'9';c=getchar())
      if(c=='-') f=-1;
    for(;c>='0'&&c<='9';c=getchar())
      k=(k<<3)+(k<<1)+c-48;
    return k*f;
}
int main(){
    int n=read();
    for(int i=1;i<=n;i++) a[i]=read();
    int m=read(); build(1,n,1);
    for(int i=1;i<=m;i++){
        int x=read(),y=read();
        zzz k=query(1,n,1,x,y);
        printf("%lld
",max(k.ll,max(k.lm,k.lr)));
    }
    return 0;
}

(2):SPOJ 1716

GSS3 - Can you answer these queries III

这题和上题相比,就多了一个单点修改,加一个修改函数就可以了

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ls p<<1
#define rs p<<1|1
#define mid ((l+r)>>1)
#define inf 0x7ffffffffLL
#define LL long long
using namespace std;
struct zzz{
    LL ll,lr,lm,sum;
}tree[200010<<2];
int a[200010];
inline void up(int p){
    tree[p].sum=tree[ls].sum+tree[rs].sum;
    tree[p].ll=max(tree[ls].ll,tree[ls].sum+tree[rs].ll);
    tree[p].lr=max(tree[rs].lr,tree[rs].sum+tree[ls].lr);
    tree[p].lm=max(tree[ls].lr+tree[rs].ll,max(tree[ls].lm,tree[rs].lm));
}
void build(int l,int r,int p){
    if(l==r){
        tree[p].sum=tree[p].ll=tree[p].lr=tree[p].lm=a[l];
        return ;
    }
    build(l,mid,ls); build(mid+1,r,rs);
    up(p);
}
zzz query(int l,int r,int p,int nl,int nr){
    zzz a={-inf,-inf,-inf,-inf},b={-inf,-inf,-inf,-inf},ans={-inf,-inf,-inf,-inf};
    if(l>=nl&&r<=nr) return tree[p];
    if(nl<=mid) a=query(l,mid,ls,nl,nr);
    if(nr>mid) b=query(mid+1,r,rs,nl,nr);
    ans.sum=a.sum+b.sum;
    ans.ll=max(a.ll,a.sum+b.ll);
    ans.lr=max(b.lr,b.sum+a.lr);
    ans.lm=max(a.lr+b.ll,max(a.lm,b.lm));
    return ans;
}
void update(int l,int r,int p,int nn,int k){
    if(l==r){
        tree[p].ll=tree[p].lm=tree[p].lr=tree[p].sum=k;
        return ;
    }
    if(nn<=mid) update(l,mid,ls,nn,k);
    if(nn>mid) update(mid+1,r,rs,nn,k);
    up(p);
}
int read(){
    int k=0,f=1; char c=getchar();
    for(;c<'0'||c>'9';c=getchar())
      if(c=='-') f=-1;
    for(;c>='0'&&c<='9';c=getchar())
      k=(k<<3)+(k<<1)+c-48;
    return k*f;
}
int main(){
    int n=read();
    for(int i=1;i<=n;i++) a[i]=read();
    int m=read(); build(1,n,1);
    for(int i=1;i<=m;i++){
        int f=read(),x=read(),y=read();
        if(f==1){
            zzz k=query(1,n,1,x,y);
            printf("%lld
",max(k.ll,max(k.lm,k.lr)));
        }
        else  update(1,n,1,x,y);
    }
    return 0;
}

以上是关于线段树维护区间最大子段和的主要内容,如果未能解决你的问题,请参考以下文章

线段树习题 总结

[题解](线段树最大连续子段和)POJ_3667_Hotel

Can you answer these queries I SPOJ - GSS1 (线段树维护区间连续最大值/最大连续子段和)

线段树 信息维护 单点修改你能回答这些问题吗

CODEVS 3981(求最大子段和+线段树)

p1115 最大子段和(线段树)