线段树:我还是很强的
简略讲解
要用线段树维护区间,我们要明确:
- 线段树存什么东西
- 怎么合并
- 如果有区间修改,怎么打标记
对于区间最大子段和,我们可以记录四个值:以维护的区间左端点为起点的最大子段和,以维护的区间右端点为终点的最大子段和,在维护区间内的最大子段和 和维护区间所有元素的和
合并的话稍微麻烦一些,看代码吧:
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\n",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\n",max(k.ll,max(k.lm,k.lr))); } else update(1,n,1,x,y); } return 0; }