小白逛公园 「解题报告」
小白逛公园
简化题意
有长度为 \(n (1\leq n\leq 5e5)\) 的序列,共 \(m(1\leq m \leq 1e5)\) 个操作。
- 求\([l,r]\)中的最大子段和
- 单点修改
解析
考虑到单次求最大子段和的时间复杂度是\(o(n)\)的,\(o(mn)\)显然无法通过此题,考虑数据结构。
Luogu P1115最大子段和 \(<— o(n)\)求最大子段和看这里
我们很容易通过动态区间查询
(看标签)想到线段树。
对于如何用线段树跑过这道题,首先得思考两个问题
-
区间如何合并
-
区间标记如何下放(这题是单点修改,显然不需要考虑这个问题)
那么我们考虑第一个问题。
若下图是线段树的一部分,A为B,C的父亲,如何用B,C来updateA呢?
可以发现,如果我们已知B,C区间的最大子段,那么A区间的最大子段有三种情况:
- 只在B区间内,等于B区间的最大子段
- 只在C区间内,等于C区间的最大子段
- 跨B,C区间
前两种很好做,考虑第三种情况。
设下图中k为A区间的最大子段,k'是k在B区间的部分,k''是k在C区间的部分。
显然有个性质:k'一定是B区间内右端点必选时的最大子段;k''一定是C区间内左端点必选时的最大子段。
证明很简单,若k'不是B区间内右端点必选时的最大子段,说明存在u使\(\sum_{i=u}^{i=n}{B_i}> \sum{k'} (u<v)\) u为B区间内一点,v为k'的左端点,n为B的右端点。此时\(\sum_{i=u}^{i=t}{A_i}>\sum{k_i}\),k不为A的最大子段,这与原设矛盾。对于k‘’同理可证。
所以对于第三种情况,我们维护线段树每个结点所表示的区间内,区间左端点必选的最大子段和与右端点必选的最大子段和。
每次update,取三种情况的max即可。
其次,考虑如何维护上述的最值。
一个节点表示的区间内,左端点必选的最大子段和=max(左儿子表示区间左端点必选的最大子段和,左儿子表示的区间和+右儿子表示区间左端点必选的最大子段和) ;右端点必选同理。
证明过程与之前类似。
至此,我们便得到了本题的思路与做法:
线段树要维护以下信息
sum—最大子段和;lsum—左端点必选的最大子段和;rsum—右端点必选的最大子段和;asum—区间和
得出Pushup函数
inline void pushup(int x) { tree[x].sum=max(tree[x<<1].sum,max(tree[x<<1|1].sum,tree[x<<1].rsum+tree[x<<1|1].lsum)); tree[x].lsum=max(tree[x<<1].lsum,tree[x<<1].asum+tree[x<<1|1].lsum); tree[x].rsum=max(tree[x<<1|1].rsum,tree[x<<1|1].asum+tree[x<<1].rsum); tree[x].asum=tree[x<<1].asum+tree[x<<1|1].asum; } |
实现/代码
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m;
struct node {
int num;
int l,r;
int sum,lsum,rsum,asum;
}tree[500010<<2];
inline void pushup(int x) {
tree[x].sum=max(tree[x<<1].sum,max(tree[x<<1|1].sum,tree[x<<1].rsum+tree[x<<1|1].lsum));
tree[x].lsum=max(tree[x<<1].lsum,tree[x<<1].asum+tree[x<<1|1].lsum);
tree[x].rsum=max(tree[x<<1|1].rsum,tree[x<<1|1].asum+tree[x<<1].rsum);
tree[x].asum=tree[x<<1].asum+tree[x<<1|1].asum;
}
void Build(int x,int l,int r) {
tree[x].num=x; tree[x].l=l; tree[x].r=r;
if(l==r) {
scanf("%d",&tree[x].sum);
tree[x].lsum=tree[x].rsum=tree[x].asum=tree[x].sum;
return;
}
int mid=(l+r)>>1;
Build(x<<1,l,mid);
Build(x<<1|1,mid+1,r);
pushup(x);
}
void Update(int x,int q,int k) {
if(tree[x].l==tree[x].r) {
tree[x].sum=tree[x].lsum=tree[x].rsum=tree[x].asum=k;
return;
}
int mid=(tree[x].l+tree[x].r)>>1;
if(q<=mid) Update(x<<1,q,k);
if(q>mid) Update(x<<1|1,q,k);
pushup(x);
}
node Query(int x,int l,int r) {
if(tree[x].l>=l&&tree[x].r<=r) return tree[x];
int mid=(tree[x].l+tree[x].r)>>1;
bool rl=0,rr=0;
node a,b;
if(l<=mid) {
rl=1;
a=Query(x<<1,l,r);
}
if(r>mid) {
rr=1;
b=Query(x<<1|1,l,r);
}
node ans;
if(rl==1&&rr==0) ans=a;
if(rl==0&&rr==1) ans=b;
if(rl==1&&rr==1) {
ans.sum=max(a.sum,max(b.sum,a.rsum+b.lsum));
ans.lsum=max(a.lsum,a.asum+b.lsum);
ans.rsum=max(b.rsum,b.asum+a.rsum);
ans.asum=a.asum+b.asum;
}
return ans;
}
int main() {
scanf("%d%d",&n,&m);
Build(1,1,n);
int o,x,y;
for(register int i=1;i<=m;i++) {
scanf("%d",&o);
if(o==1) {
scanf("%d%d",&x,&y);
if(x>y)swap(x,y);
printf("%d\n",Query(1,x,y).sum);
}
if(o==2) {
scanf("%d%d",&x,&y);
Update(1,x,y);
}
}
return 0;
}