GSS2 - Can you answer these queries II(线段树-双延迟标记)

题目链接

题目很长,题意却很简单。
询问任意区间内,最大连续子序列和(其中相等的值不重复计算),可以不选输出0;

1、不存在修改操作
2、子序列中相等的值不重复计算
3、可以选为空的子序列,结果为0
4、数据都在1e5内,其中序列中每个数的绝对值都在1e5内
输入:
1、n个数(总序列长度)
2、下面一行n个数(有正有负)
3、m次询问
4、每次询问[x,y]区间内,最大连续子序列和,其中相等的值不重复计算

直接看样例

样例
9
4 -2 -2 3 -1 -4 2 2 -6
3
1 2
1 5
4 9
输出
4
5
3

有一道比较简单、和这题类似的题目:HDU - 3333Turing Tree
它那题意大致为 : 查询静态区间 i - j 内不同数之和。
里面的离线处理,比起在线的树套树的主席树要简单得多

这种不带修改的题一般可以用离线处理走捷径。把每次加值,看成一次修改。这是刷题可以刷出来的经验。


但这题最难的是如何构造线段树,定义哪些变量,这题我没想出来,看了答案,算作一次经验吧。

题解:

设线段树的叶子为s[i], 每次更新 a[i]的时候,
1、a[i]之前没出现过,s[1] -s[i]区间内加上a[i];那么
s[1] = a[1] + a[2] + a[3] + … + a[i];
s[2] = a[2] + a[3] + … + a[i];
s[3] = a[3] + … + a[i];

s[i] = a[i];
2、a[i]在第k位出现过(mp[a[i]]==k),s[k+1] -s[i]区间内加上a[i];那么
s[k+1] = a[k+1] + a[k+2] + a[k+3] + … + a[i];
s[k+2] = a[k+2] + a[k+3] + … + a[i];

s[i] = a[i];
转换成区间修改(统一加一个值)的效果,修改范围为[ mp[a[i]]+1 , i ] 。

基础构建完成后,该怎么设计延迟变量来传递其中的关系。

这里引用双延迟标记,一个pre,与mx挂钩,一个lazy,与num挂钩。一般你想不到上面这种模型的构建,也不会想到双延迟标记。

下面是定义+推导

各个变量的含义:
加到第i个值时
num		当前状态下的最大值		如:max(s[1],s[2],.....s[i])(头元素)
当然其左右儿子分别表示max(s[1],..s[i/2])max(s[i/2+1],..s[i])	其他情况类似

lazy	正常的延迟标记			将相加的值延迟
---------------------------------------------------
但上述两个变量只有当前情况的最大值,过去可能存在,如
a[x]+...+a[y]	(y<i,1<=x<=y)是最大的
上面的情况是之前加到第y个时num的值(y时刻下的s[x])
这种过程在推的过程中是不可回溯的(因为会覆盖)
-->所以要储存过去的情况
---------------------------------------------------
很显然想到设一个mx,表示过去和现在的最大值
-->所以更新时用  t[i].mx=max(t[i].mx,t[i].num);
---------------------------------------------------
而又对于每个mx普通的一个个比较又不好(从1遍历到i)
这里又可以优化,再加一个延迟变量pre
---------------------------------------------------
线段树结构是非线性的(虽然存储是线性的),如果你实在不理解,先回归线性情况
我们对于一个固定序列,求最大子序列和(没有去重情况),
加到第i个,我们通常会求后面的最大相加值,即pre
update更新中,t[i].pre=max(t[i].pre,t[i].lazy);
下推时,pre的变化	t[ls].pre=max(t[ls].pre,t[ls].lazy+t[i].pre);
		mx的变化	t[ls].mx=max(t[ls].mx,t[ls].num+t[i].pre);
---------------------------------------------------
综上所述
lazy就正常理解的延迟标记,而pre是从lazy推过来的
num是当前(片面)的最大值,是辅助推mx值的
pre,mx是主要变量
num,lazy是记录变化过程
---------------------------------------------------
实在理解不了,自己逐步推一遍吧

注意点:
1、pre很显然是非负的,可以验证上面思路
2、下推时,对于左右结点,先确定主要值pre,mx,再修改num,lazy这两影响进一步下推的变量
	顺序不要搞错。
3、区间相加基操要熟练

关于离线处理
这种思维方式,在线段树中很常见。列举几道题
hdu 3333 Turing Tree
hdu 3874 Necklace
Codeforces Round #136 (Div. 2) D. Little Elephant and Array(附其他人的一个题解

总结:
大知识点是线段树区间求和,有三个难点:1、线段树的构建。2、离线处理。3、双延迟标记。题目繁琐,但题意简易,却又不好做。

#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int maxn=5e5+7;
ll read(){
    ll x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+ch-'0';
        ch=getchar();
    }
    return x*f;
}
int n,m;
ll a[maxn];
#define ls		i<<1
#define rs		i<<1|1
#define lson	ls,l,mid
#define rson	rs,mid+1,r
map<int,int>mp;
struct node{
	ll lazy,num,mx,pre;
}t[maxn];
typedef struct choice{
	int l,r,id;
	ll ans;
}p;
p sz[maxn];

void pushup(int i){
	t[i].num=max(t[ls].num,t[rs].num);
	t[i].mx=max(t[ls].mx,t[rs].mx);
}
void pushdown(int i){
	if(t[i].lazy||t[i].pre){
		ll j=t[i].pre,k=t[i].lazy;
		t[i].pre=t[i].lazy=0;
		t[ls].pre=max(t[ls].pre,t[ls].lazy+j);
		t[rs].pre=max(t[rs].pre,t[rs].lazy+j);
		t[ls].mx=max(t[ls].mx,t[ls].num+j);
		t[rs].mx=max(t[rs].mx,t[rs].num+j);
		t[ls].lazy+=k;	t[rs].lazy+=k;
		t[ls].num+=k;	t[rs].num+=k;
	}
}
void update(int i,int l,int r,int x,int y,int c){
	if(l>y||r<x)	return;
	if(x<=l&&r<=y){
		t[i].num+=c;	t[i].lazy+=c;
		t[i].pre=max(t[i].pre,t[i].lazy);
		t[i].mx=max(t[i].mx,t[i].num);
		return;
	}
	pushdown(i);
	int mid=l+r>>1;
	update(lson,x,y,c);	update(rson,x,y,c);
	pushup(i);
}
ll query(int i,int l,int r,int x,int y){
	if(l>y||r<x)	return 0;
	if(x<=l&&r<=y)	return t[i].mx;
	pushdown(i);
	int mid=l+r>>1;
	return max(query(lson,x,y),query(rson,x,y));
}
bool cmp1(p x,p y){	return x.r<y.r;	}
bool cmp2(p x,p y){	return x.id<y.id;	}
int main(){
	n=read();
	for(int i=1;i<=n;i++)	a[i]=read();
	m=read();
	for(int i=0;i<m;i++){
		sz[i].l=read();	sz[i].r=read();
		sz[i].id=i;
	}
	sort(sz,sz+m,cmp1);
	int k=0;
	for(int i=1;i<=n;i++){
		update(1,1,n,mp[a[i]]+1,i,a[i]);
		mp[a[i]]=i;
		while(k<m&&sz[k].r==i){
			sz[k].ans=query(1,1,n,sz[k].l,i);
			k++;
		}
	}
	sort(sz,sz+m,cmp2);
	for(int i=0;i<m;i++)	printf("%lld\n",sz[i].ans);
}
/*
9
4 -2 -2 3 -1 -4 2 2 -6
3
1 2
1 5
4 9

4
5
3
*/
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值