spoj GSS 1-8的解析——线段树维护最大子段和一类问题的技巧(持续更新中)

题目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;
}

持续更新中...

猜你喜欢

转载自blog.csdn.net/hzk_cpp/article/details/80637425