GSS2 - Can you answer these queries II 题解

本文介绍了一种利用线段树解决复杂子段和问题的方法,通过构造新序列并结合懒惰传播,实现了高效的区间更新与查询操作。适用于处理大规模数据集上的动态区间问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目传送门

这道题是真的厉害——线段树的神仙应用。

正解

对于原序列直接建一棵线段树去维护信息的话几乎不可做,所以要换一个思路。

考虑造一个新序列,每一个点表示原序列中从这个点开始向右延伸能得到的最大子段和。用线段树去维护这个东西,所以这个序列并不用在代码中体现。

然后我们将所有询问进行排序,按右端点为关键字,然后将原序列从左到右过一遍,设原序列为 a a a,当前遍历到第 i i i位,此时将线段树上 p o s [ a i ] + 1 pos[a_i]+1 pos[ai]+1 ~ i i i这一段加 a i a_i ai p o s [ a i ] pos[a_i] pos[ai]表示 a i a_i ai这个数上一次出现的位置。这样就搞定了相同的数只算一次这个条件。

线段树上每个节点需要维护4个东西:

  1. lazy标记,用处和正常的lazy标记一样
  2. lazymax,因为lazy是会变化的,lazymax就用来记录lazy的最大值
  3. maxnow,记录这个节点管理的区间内的最大值
  4. maxold,记录maxnow的最大值,类似lazymax

显然,处理完第 i i i位之后,就可以统计右端点为 i i i的询问的答案,答案就是 l l l ~ r r r区间内最大的maxold。

仔细一想,发现lazy标记的意义也就是向右延伸到第 i i i位会增加多少,而lazymax的作用就是找最优的延伸方案。

那么上面这几个东西都是很好维护的,重点在pushdown这个函数,也就是下传标记。

代码是这样的:

void pushdown()
{
	if(zuo!=NULL)//zuo,you是指左右儿子,我用指针写的
	{
		zuo->lazymax=max(zuo->lazymax,zuo->lazy+lazymax);
		you->lazymax=max(you->lazymax,you->lazy+lazymax);
		
		zuo->maxold=max(zuo->maxold,zuo->maxnow+lazymax);
		you->maxold=max(you->maxold,you->maxnow+lazymax);
		
		zuo->lazy+=lazy;you->lazy+=lazy;
		zuo->maxnow+=lazy;you->maxnow+=lazy;
	}
	lazy=lazymax=0;
}

清楚这四个元素的定义后,相信这个pushdown也不难理解。

这里有个需要注意的地方,一般线段树pushdown的时候需要考虑lazy是否为0,如果为0就不pushdown了,但是这个判断这里不能用,因为还有lazymax需要下传。(我就是因为犯了这种沙雕错误调了半天555)

完整代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 100010
#define ll long long

int n,m;
int a[maxn];
struct que{int x,y,z;};
que ask[maxn];
struct node{
	int l,r;
	ll lazy,lazymax,maxnow,maxold;
	node *zuo,*you;
	node():l(0),r(0),lazy(0),lazymax(0),maxnow(0),maxold(0),zuo(NULL),you(NULL){};
	void build(int x,int y)
	{
		l=x,r=y;
		if(l<r)
		{
			int mid=x+y>>1;
			zuo=new node();zuo->build(x,mid);
			you=new node();you->build(mid+1,y);
		}
	}
	void pushdown()
	{
		if(zuo!=NULL)
		{
			zuo->lazymax=max(zuo->lazymax,zuo->lazy+lazymax);
			you->lazymax=max(you->lazymax,you->lazy+lazymax);
			
			zuo->maxold=max(zuo->maxold,zuo->maxnow+lazymax);
			you->maxold=max(you->maxold,you->maxnow+lazymax);
			
			zuo->lazy+=lazy;you->lazy+=lazy;
			zuo->maxnow+=lazy;you->maxnow+=lazy;
		}
		lazy=lazymax=0;
	}
	void change(int x,int y,ll z)
	{
		if(l==x&&r==y)
		{
			lazymax=max(lazymax,lazy+=z);//注意里面是+=而不是+
			maxold=max(maxold,maxnow+=z);
			return;
		}
		pushdown();
		int mid=l+r>>1;
		if(y<=mid)zuo->change(x,y,z);
		else if(x>=mid+1)you->change(x,y,z);
		else zuo->change(x,mid,z),you->change(mid+1,y,z);
		maxnow=max(zuo->maxnow,you->maxnow);//记得更新自己的答案
		maxold=max(zuo->maxold,you->maxold);
	}
	ll getmax(int x,int y)
	{
		if(l==x&&r==y)return maxold;
		pushdown();
		int mid=l+r>>1;
		if(y<=mid)return zuo->getmax(x,y);
		else if(x>=mid+1)return you->getmax(x,y);
		else return max(zuo->getmax(x,mid),you->getmax(mid+1,y));
	}
};
node *root=new node();
bool cmp(que x,que y){return x.y<y.y;}
int pos[3*maxn];
ll ans[maxn];

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%d",&a[i]);
	root->build(1,n);
	scanf("%d",&m);
	for(int i=1;i<=m;i++)
	scanf("%d %d",&ask[i].x,&ask[i].y),ask[i].z=i;
	sort(ask+1,ask+m+1,cmp);//将询问按右端点排序
	int now=1;
	for(int i=1;i<=n;i++)
	{
		root->change(pos[a[i]+maxn]+1,i,(ll)a[i]);
		pos[a[i]+maxn]=i;
		while(ask[now].y==i&&now<=m)ans[ask[now].z]=root->getmax(ask[now].x,ask[now].y),now++;//统计答案
		if(now>m)break;//如果所有询问都做完了,就退出
	}
	for(int i=1;i<=m;i++)
	printf("%lld\n",ans[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值