蓝桥杯国赛训练营:小白逛公园(线段树维护最大区间子段和)

题目大意:有一个长为n的序列,每次询问[l,r]区间内最大子段和是多大。
徒手撸应该能撸出来(我撸不出来)
没学过的话写起来还是有点困难的,一点一点看吧。

struct ss{
 	int l,r;				//结点维护的区间
 	int lv,rv,mv,sum;		//左起最大连续子段和,右起最大连续子段和,整个区间的最大连续子段和,整个区间的和
}tree[maxn << 2];

为什么要维护左起最大连续子段和、和右起最大连续子段和呢?因为一个区间的最大子段和可能在左起开始连续的一段或者右起开始连续的一段,这个时候维护这个连续段需要 维护子结点的lv,rv(含义和如上面描述),回溯更新当前结点的 lv,rv 和mv。除此之外,我们要想上维护更新也必须维护lv,rv。因为mv并不知道在哪个段内,目前来看mv对于维护区间子段和没什么用,而通过lv,rv和区间分割我们总能凑出中间连续的一段子段和。

那为什么又要维护mv呢?第一点,如果不维护mv,查找将变得巨麻烦,虽然你所查找的任何连续区间都可以通过子节点的lv,rv拼凑,但是没有mv你查不出你要查找的区间的最大子段和,也只通过合并lv,rv是找不到最大子段和的(除非暴力去搜你要查找的区间内所有连续的区间段,得对递归有深厚的代码功底才能码出来,然后还TLE)。

维护mv后,思考一下,假如我们要查找的区间是[l,r],我们可以通过在线段树上不断的对区间进行分割,对于根节点,如果查找区间[l,r]跨了中间,我们可以分成[l,mid],[mid + 1,r](mid 为 根节点维护的区间的中点),在左子树查出[l,mid] 的最大子段和,右子树查找[mid + 1,r]的最大子段和,以及两段区间的lv,和rv,通过合并比较,可以求出[l,r]的最大子段和(初学这里可能不理解,或者不熟练(比如我) )。

先将如何维护mv吧,对于一个区间,将这个区间分成两半,这个区间的mv可能是左边部分的mv(递归),或右边部分的mv(递归),这两种情况是mv 分布在坐半部分或在右半部分,但还有一种可能,那就是mv分布在跨过中点的那一段,如果mv是这一段,那mv一定是左半区间的rv + 右边区间的lv (想一想为什么),这里也体现了rv,lv的作用。实际上rv ,lv,mv都是需要维护的东西,不用分开思考各个的意义。所以一个区间的mv是上述三种情况的最大值。

既然区间的mv,可以由子区间的各自信息最终拼凑出来,那么我们要查找的l,r 是否也可以由子区间拼凑出来?显然是的,但是怎么分割区间 l,r (就和其他的查询一模一样啊,不断的分割查找区间在哪个结点,然后查找就完事了)? 我们的查询要基于线段树,如果 l ,r 在有子树,就直接递归到右子树查找,如果 l ,r在左子树,就递归到左子树查找,如果l,r跨区间了,那么先到左子树找左半边,然后在右子树找右半边,然后合并两半的区间维护 总区间的最大值(怎么维护?上面不是讲了吗?自己想想)

然后这题怎么做?这么裸了直接肝啊,维护什么的上面都讲了,直接贴出代码吧

贴出代码(还是要自己多多理解啊)

#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 10;
int n,m;
int a[maxn];
struct ss{
 	int l,r;
 	int lv,rv,mv,sum;
}tree[maxn << 2];
void pushup(int rt){
 	tree[rt].lv = max(tree[rt << 1].lv,tree[rt << 1].sum + tree[rt << 1 | 1].lv);
 	tree[rt].rv = max(tree[rt << 1 | 1].rv,tree[rt << 1 | 1].sum + tree[rt << 1].rv);
 	tree[rt].mv = max(max(tree[rt << 1].mv,tree[rt << 1 | 1].mv),tree[rt << 1].rv + tree[rt << 1 | 1].lv);
 	tree[rt].mv = max(tree[rt].mv,max(tree[rt << 1].rv,tree[rt << 1 | 1].lv));
 	tree[rt].sum = tree[rt << 1].sum + tree[rt << 1 | 1].sum;
}
void build(int rt,int l,int r){
 	tree[rt].l = l;tree[rt].r = r;
 	if(tree[rt].l == tree[rt].r){
 	 	tree[rt].lv = tree[rt].rv = tree[rt].mv = tree[rt].sum = a[l];
  		return ;
 	}
 	int mid = l + r >> 1;
 	build(rt << 1,l,mid);
 	build(rt << 1 | 1,mid + 1,r);
 	pushup(rt);
}
void update(int rt,int p,int k){
 	if(tree[rt].l == tree[rt].r){
  		tree[rt].lv = tree[rt].rv = tree[rt].mv = tree[rt].sum = k;
   		return ;
 	}
 	int mid = tree[rt].l + tree[rt].r >> 1;
 	if(p > mid) update(rt << 1 | 1,p,k);
 	else update(rt << 1,p,k);
 	pushup(rt);
}
ss query(int rt,int l,int r){
 	if(tree[rt].l >= l && tree[rt].r <= r) return tree[rt];
 	int mid = tree[rt].l + tree[rt].r >> 1;
 	if(l <= mid && r > mid){   //如果跨了两个区间 
  		ss x1 = query(rt << 1,l,mid);
  		ss x2 = query(rt << 1 | 1,mid + 1,r);
  		ss ans;
  		ans.lv = max(x1.lv,x1.sum + x2.lv);
  		ans.rv = max(x2.rv,x2.sum + x1.rv);
  		ans.mv = max(max(x1.mv,x2.mv),x1.rv + x2.lv);
  		ans.mv = max(ans.mv,max(x1.rv,x2.lv));
  		ans.sum = x1.sum + x2.sum;
  		return ans;
 	}
 	else{
  		if(l > mid) return query(rt << 1 | 1,l,r);
  		else return query(rt << 1,l,r);
 	}
}
int main(){
 	scanf("%d%d",&n,&m);
 	for(int i = 1; i <= n; i++){
 		scanf("%d",&a[i]);
	}
 	build(1,1,n);
 	for(int i = 1; i <= m; i++){
  		int f,x,y;
  		scanf("%d%d%d",&f,&x,&y); 
  		if(f == 1){
   			if(x > y) swap(x,y);
   			printf("%d\n",query(1,x,y).mv);
  		}
  		else{
   			update(1,x,y);
  		}
 	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值