题目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; }
持续更新中...