小白逛公园 「解题报告」

小白逛公园


Luogu / Vijos

简化题意

有长度为 \(n (1\leq n\leq 5e5)\) 的序列,共 \(m(1\leq m \leq 1e5)\) 个操作。

  1. \([l,r]\)中的最大子段和
  2. 单点修改

解析

考虑到单次求最大子段和的时间复杂度是\(o(n)\)的,\(o(mn)\)显然无法通过此题,考虑数据结构。

Luogu P1115最大子段和 \(<— o(n)\)求最大子段和看这里


我们很容易通过动态区间查询(看标签)想到线段树。


对于如何用线段树跑过这道题,首先得思考两个问题

  • 区间如何合并

  • 区间标记如何下放(这题是单点修改,显然不需要考虑这个问题)

那么我们考虑第一个问题。

若下图是线段树的一部分,A为B,C的父亲,如何用B,C来updateA呢?

可以发现,如果我们已知B,C区间的最大子段,那么A区间的最大子段有三种情况:

  1. 只在B区间内,等于B区间的最大子段
  2. 只在C区间内,等于C区间的最大子段
  3. 跨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&lt;iostream>
#include&lt;cstdio>
#include&lt;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;
}
posted @ 2019-07-26 16:35  jht_cnblogs  阅读(201)  评论(0编辑  收藏  举报