spoj GSS 1-8的解析――线段树维护最大子段和一类问题的技巧

匿名 (未验证) 提交于 2019-12-03 00:30:01

题目spoj,但是太卡了,无奈之下水luogu.

T1

给定一个序列,有M此操作,每次操作查询[l,r]的最大子段和,不可取空序列.

一道水题,我们可以得知,区间[l,r]的最大子段和只有三种可能:左半边最大子段和,右半边最大子段和,左半边一定包括最右端的最大子段和+右半边一定包括最左端的最大子段和.

于是我们线段树每个节点就维护四个信息:最大子段和sum,一定包括最左端最大子段和lsum,一定包括最右端最大子段和rsum,以及区间和ans.

那么我们就可以写出代码:

#include<bits/stdc++.h>   using namespace std; typedef long long LL; const int N=100000; struct tree{   int l,r;   int sum,lsum,rsum,ans; }tr[N*5]; int n,m,a[N+1]; void build(int L,int R,int k=1){   tr[k].l=L;tr[k].r=R;   if (L==R){     tr[k].ans=tr[k].sum=tr[k].lsum=tr[k].rsum=a[L];     return;   }   int mid=L+R>>1;   build(L,mid,k<<1);   build(mid+1,R,k<<1|1);   tr[k].ans=tr[k<<1].ans+tr[k<<1|1].ans;   tr[k].lsum=max(tr[k<<1].ans+tr[k<<1|1].lsum,tr[k<<1].lsum);   tr[k].rsum=max(tr[k<<1|1].ans+tr[k<<1].rsum,tr[k<<1|1].rsum);   tr[k].sum=max(max(tr[k<<1].sum,tr[k<<1|1].sum),tr[k<<1].rsum+tr[k<<1|1].lsum); } struct tree1{   int ans,sum,lsum,rsum; }; tree1 query(int L,int R,int k=1){   if (L==tr[k].l&&R==tr[k].r) return (tree1){tr[k].ans,tr[k].sum,tr[k].lsum,tr[k].rsum};   int mid=tr[k].l+tr[k].r>>1;   if (R<=mid) return query(L,R,k<<1);   else if (L>mid) return query(L,R,k<<1|1);     else {       tree1 u=query(L,mid,k<<1),v=query(mid+1,R,k<<1|1),o;       o.ans=u.ans+v.ans;       o.lsum=max(u.ans+v.lsum,u.lsum);       o.rsum=max(v.ans+u.rsum,v.rsum);       o.sum=max(max(u.sum,v.sum),u.rsum+v.lsum);       return o;     } } inline void into(){   scanf("%d",&n);   for (int i=1;i<=n;i++)     scanf("%d",&a[i]); } inline void work(){   build(1,n); } inline void outo(){   scanf("%d",&m);   int x,y;   for (int i=1;i<=m;i++){     scanf("%d%d",&x,&y);     printf("%d\n",query(x,y).sum);   } } int main(){   into();   work();   outo();   return 0; }

T2:题目与第一题不同的是询问时,若子段中有相同的元素,只能算一个,且可取空序列,其他与GSS1相同.

这道题如果要做的话,因为题目只有修改操作,没有查询操作,所以我们先给询问区间离线搞下来,按照右端点排序.

排序之后,我们可以给它搞几个修改操作,也就是说,一开始整棵树为空.

之后从i从1枚举到n,每一次都进行一个修改,以及回答右端点为i的询问.

那么我们定义一棵线段树,假设当前扫到了第i个点,那么其中第j个叶子节点表示的就是区间[j,i].

那么我们线段树的非叶子节点其实就没有用了,可以什么信息也不存,只存标记.

我们现在用ma表示区间[j,i]中必须包括右端点i的最大字段和,由于我们不一定要取右端点,所以我们再添加一个hma表示历史最大值(即答案).

那么我们考虑add操作,由于我们要让最大子段和之中相等的数只算一个,那我们设与第i个数相等的数在最后面的位置是last,则我们要修改的区间就是[last+1,i],所以我们要学会打标记.

所以我们再加两个元素tag和htag,分别表示增量总和和历史最大增量,其中历史最大增量就是指增量出现过的最大值.

之后在找到了需要增加的区间的时候,我们可以让tag先加上这个数,然后htag取当前tag和htag的最大值.

若这个区间是叶子节点,我们则需要将ma加上num,且hma取ma和hma的最大值.

当下传懒标记也就是pushdown的时候,我们要将k的儿子的tag都加上tr[k].tag,k的儿子的ma加上tr[k].tag.

但是当时历史最大时,我们发现这个标记节点k的祖先肯定没有任何标记了,也就是说tr[k].htag一定是一段连续的,而且显然的,tr[k的儿子].ma和tr[k的儿子].tag肯定也是连续的一段,并且这一段肯定是紧贴tr[k].htag的,所以我们可以得出结论:

tr[k.son].hma=max(tr[k.son].hma,tr[k.son].ma+tr[k].htag).

tr[k.son].htag=max(tr[k.son].htag,tr[k.son].tag+tr[k].htag).

注意hma一定要放在ma前更新,htag一定要放在tag钱更新.

那么这道题就很简单毒瘤了.

代码如下:

#include<bits/stdc++.h>   using namespace std; typedef long long LL; const int N=500000; struct tree{   int l,r;   LL ma,hma,tag,htag; }tr[N*5]; struct question{   int l,r,id; }q[N+1]; int n,m,now[N+1],last[N+1],lx[N+1]; LL a[N+1],ans[N+1]; inline void into(){   scanf("%d",&n);   for (int i=1;i<=n;i++)     scanf("%lld",&a[i]);   scanf("%d",&m);   for (int i=1;i<=m;i++)     scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i; } bool cmp(question a,question b){   return a.r<b.r; } void build(int L,int R,int k=1){   tr[k].l=L;tr[k].r=R;   if (L==R) return;   int mid=L+R>>1;   build(L,mid,k<<1);build(mid+1,R,k<<1|1); } void pushdown(int k){   int ls=k<<1,rs=ls|1;   tr[ls].htag=max(tr[ls].htag,tr[ls].tag+tr[k].htag);   tr[rs].htag=max(tr[rs].htag,tr[rs].tag+tr[k].htag);   tr[ls].hma=max(tr[ls].hma,tr[ls].ma+tr[k].htag);   tr[rs].hma=max(tr[rs].hma,tr[rs].ma+tr[k].htag);   tr[ls].tag+=tr[k].tag;tr[rs].tag+=tr[k].tag;   tr[ls].ma+=tr[k].tag;tr[rs].ma+=tr[k].tag;   tr[k].tag=tr[k].htag=0LL; } void pushup(int k){   tr[k].ma=max(tr[k<<1].ma,tr[k<<1|1].ma);   tr[k].hma=max(tr[k<<1].hma,tr[k<<1|1].hma); } void add(int L,int R,LL num,int k=1){   if (tr[k].l==L&&tr[k].r==R){     tr[k].ma+=num;     tr[k].tag+=num;     tr[k].htag=max(tr[k].htag,tr[k].tag);     tr[k].hma=max(tr[k].hma,tr[k].ma);     return;   }   pushdown(k);   int mid=tr[k].l+tr[k].r>>1;   if (R<=mid) add(L,R,num,k<<1);   else if (L>mid) add(L,R,num,k<<1|1);     else add(L,mid,num,k<<1),add(mid+1,R,num,k<<1|1);   pushup(k); } LL query(int L,int R,int k=1){   if (tr[k].l==L&&tr[k].r==R) return tr[k].hma;   pushdown(k);   int mid=tr[k].l+tr[k].r>>1;   if (R<=mid) return query(L,R,k<<1);   else if (L>mid) return query(L,R,k<<1|1);     else return max(query(L,mid,k<<1),query(mid+1,R,k<<1|1)); } inline void work(){   for (int i=1;i<=n;i++)     last[i]=lx[a[i]+100000],lx[a[i]+100000]=i;   sort(q+1,q+1+m,cmp);   build(1,n);   int j=1;   for (int i=1;i<=n;i++){     add(last[i]+1,i,a[i]);     for (;j<=m&&q[j].r==i;j++) ans[q[j].id]=query(q[j].l,q[j].r);   } } inline void outo(){   for (int i=1;i<=m;i++)     printf("%lld\n",ans[i]); } int main(){   into();   work();   outo();   return 0; }

持续更新中...

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!