题目链接:P4513 小白逛公园
题意简述
小白对于他逛的公园,都有一个评价值。现在,他一共有两个操作需要你进行维护。
1、询问在一个区间中,最大的子区间(求区间最大子段和)
2、改变对某个公园的评价(单点修改)
input
// n个公园,m个操作 5 3 //公园的初始评价 1 2 -3 4 5 // 操作 1 2 3 2 2 -1 1 2 3
思路
构建线段树
要维护动态的区间最大子段和,则需要构建一颗可以进行查询最大子段和线段树。
1、维护区间总和sum
2、维护区间左端最大子段和lmax
3、维护区间右端最大子段和rmax
4、维护区间最大子段和mmax
其次,子节点的信息应该如何传给他的父亲节点呢?
1、对于区间总和sum,直接进行区间加即可,tr[fa].sum=tr[lc].sum+tr[rc].sum;
2、对于左端的最大子段和,可以是左子树的左端最大子段和,也可以是一整个左子树和右子树的左端子段和拼接起来
则有:tr[fa].lmax=max(tr[lc].lmax,tr[lc].sum+tr[rc].lmax);
3、对于右端的最大子段和,可以是右子树的右端最大子段和,也可以是一整个右子树和左子树的右端最大子段和拼接起来
则有:tr[fa].rmax=max(tr[rc.rmax,tr[rc].sum+tr[lc].rmax);
4、对于区间最大子段和,则有三种来源,左子树的最大子段和,右子树的最大子段和,左子树的右端最大子段和和右子树的左端最大子段和拼接起来
则有:tr[fa].mmax=max(tr[lc].mmax,tr[rc].mmax,tr[lc].rmax+tr[rc].lmax);
修改与查询
修改
修改是单点修改,不是很复杂,一直遍历到这个公园所在的叶子节点,然后直接修改,再向上pushup传递子树信息即可。
查询
对于查询操作,则有一些难度。对于一颗线段树,许多节点都是有两个子节点的,那么该如何进行传递信息呢?这也是这道题十分值得讲的一个点。
其实,对于我们的函数,不仅仅是void,int,long long,还可以自己定义返回值,比如这道题,我们的线段树的结构体为 Tree ,那么我们就可以将查询操作的返回值定义为 Tree ,那么int类型只能返回一个值的束缚就没有了。具体操作,可以看Code。
我们就可以建立一个临时节点,记录左右两种遍历所得到的信息,然后向上传即可。
Code(不足可指出)
#include<bits/stdc++.h>
#include<string>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
using namespace std;
inline int read() {
int x=0,f=1;char s=getchar();
while (s>'9'||s<'0') {
if (s=='-') f=-f;
s=getchar();
}
while (s>='0'&&s<='9') {
x=(x<<1)+(x<<3)+(s^48);
s=getchar();
}
return x*f;
}
const int N = 5e5+10;
int n,m,a[N],op,x,y;
struct Tree{
int sum,lmax,rmax,mmax;
}tr[N<<2];
void pushup(Tree &T,Tree lc,Tree rc) { // pushup 向上传递信息
T.sum=lc.sum+rc.sum;
T.lmax=max(lc.lmax,lc.sum+rc.lmax);
T.rmax=max(rc.rmax,rc.sum+lc.rmax);
T.mmax=max(lc.mmax,max(rc.mmax,lc.rmax+rc.lmax));
return;
}
void build(int k,int l,int r) { //建立线段树
if (l==r) {
tr[k]={a[l],a[l],a[l],a[l]};
return;
}
int mid=(l+r)>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
pushup(tr[k],tr[k<<1],tr[k<<1|1]);
}
void modify(int k,int l,int r,int xx,int v) {
if (l==r) { //已经到达了这个叶子节点了,修改完成之后直接返回
tr[k]={v,v,v,v};
return;
}
int mid=(l+r)>>1;
if (mid>=xx) modify(k<<1,l,mid,xx,v);
if (mid<xx) modify(k<<1|1,mid+1,r,xx,v);
pushup(tr[k],tr[k<<1],tr[k<<1|1]);
return;
}
Tree ser(int k,int l,int r,int zl,int zr) {
if (l>=zl&&r<=zr) return tr[k]; //当前这个节点被查询区间包含,直接返回即可
int mid=(l+r)>>1;
if (mid>=zr) return ser(k<<1,l,mid,zl,zr); //当mid<zr 时要遍历右子树,此时这种情况只会遍历左子树
if (mid<zl) return ser(k<<1|1,mid+1,r,zl,zr); // 同理
Tree res,lc,rc;
//res 是建立的临时的记录查询结果的节点
//lc 是查询左子树的结果
//rc 是查询右子树的结果
lc=ser(k<<1,l,mid,zl,zr);
rc=ser(k<<1|1,mid+1,r,zl,zr);
pushup(res,lc,rc);//将左右节点的信息整合,向上传递
return res;
}
int main(){
n=read();m=read();
for (int i=1;i<=n;++i) a[i]=read();
build(1,1,n);
for (int i=1;i<=m;++i) {
op=read();x=read();y=read();
if (op==1) {
if (x>y) swap(x,y);
Tree ans=ser(1,1,n,x,y);
printf("%d\n",ans.mmax);
}
else {
modify(1,1,n,x,y);
}
}
return 0;
}
到此,就结束了。