这道题是真的厉害——线段树的神仙应用。
正解
对于原序列直接建一棵线段树去维护信息的话几乎不可做,所以要换一个思路。
考虑造一个新序列,每一个点表示原序列中从这个点开始向右延伸能得到的最大子段和。用线段树去维护这个东西,所以这个序列并不用在代码中体现。
然后我们将所有询问进行排序,按右端点为关键字,然后将原序列从左到右过一遍,设原序列为
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个东西:
- lazy标记,用处和正常的lazy标记一样
- lazymax,因为lazy是会变化的,lazymax就用来记录lazy的最大值
- maxnow,记录这个节点管理的区间内的最大值
- 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]);
}