题目大意: 多组询问,求给定序列的最大子段和,并且这个子段的两端的选取范围给出。
题解
来看题解的应该有不少来看坑点的,那我先把坑点说出来:
按洛谷上的翻译,查询左端点在[x1,y1]之间,且右端点在[x2,y2]之间的最大子段和
,但实际上,也可以是右端点在[x1,y1]之间,且左端点在[x2,y2]之间的最大子段和
,也就是说只要两端在这两个区间内即可。
进入正题
这题需要大力讨论。
给定区间的情况,总的来说无非三种:
- 无交集:
- 有交集
- 一个包含另一个
还需要注意的就是给定区间的两端重合的情况需要特判罢了。
事实上,这些情况都可以拆成两种相同的情况来求解:
- 无交集
- 完全相同
为了方便,我们设
- a n s 1 ( a , b , c , d ) ans1(a,b,c,d) ans1(a,b,c,d)为第一种情况的答案,也就是区间 [ a , b ] [a,b] [a,b]与区间 [ c , d ] [c,d] [c,d]无交集,并且区间 [ a , b ] [a,b] [a,b]在区间 [ c , d ] [c,d] [c,d]的左边,在区间 [ a , b ] [a,b] [a,b]选取左端点,在区间 [ c , d ] [c,d] [c,d]选取右端点的最大子段和。
- a n s 2 ( a , b ) ans2(a,b) ans2(a,b)为在区间 [ a , b ] [a,b] [a,b]内选取左右端点的最大子段和。
怎么拆呢?
大力讨论:
设 x , y , x x , y y x,y,xx,yy x,y,xx,yy为给定的两个区间的左右端点。并且满足 x < x x ∨ ( x = x x ∧ y > y y ) x<xx \lor (x=xx \land y>yy ) x<xx∨(x=xx∧y>yy)
对于第一种情况:
a n s = a n s 1 ( x , y , x x , y y ) ans=ans1(x,y,xx,yy) ans=ans1(x,y,xx,yy)。
对于第二种情况:
a n s = max ( a n s 1 ( x , x x − 1 , x x , y y ) , a n s 1 ( x , y , y + 1 , y y ) , a n s 2 ( x x , y ) ) ans=\max(ans1(x,xx-1,xx,yy),ans1(x,y,y+1,yy),ans2(xx,y)) ans=max(ans1(x,xx−1,xx,yy),ans1(x,y,y+1,yy),ans2(xx,y))
对于第三种情况:
a n s = max ( a n s 1 ( x , x x − 1 , x x , y y ) , a n s 2 ( x x , y y ) , a n s 1 ( x x , y y , y y + 1 , y ) ) ans=\max(ans1(x,xx-1,xx,yy),ans2(xx,yy),ans1(xx,yy,yy+1,y)) ans=max(ans1(x,xx−1,xx,yy),ans2(xx,yy),ans1(xx,yy,yy+1,y))
对于 x = x x x=xx x=xx或 y = y y y=yy y=yy的情况特判即可。
现在只需要求解 a n s 1 ( a , b , c , d ) ans1(a,b,c,d) ans1(a,b,c,d)和 ( a n s 2 ( a , b ) ) (ans2(a,b)) (ans2(a,b))。
显然, a n s 2 ans2 ans2求区间最大子段和是个大裸题,做法参考GSS1。
a n s 1 ans1 ans1也很好求:显然, [ b + 1 , c − 1 ] [b+1,c-1] [b+1,c−1]这一段是必选的,所以答案先加上这一段,这个求区间和可以用前缀和来实现,但是下面的操作需要用线段树,所以这个操作我也用线段树顺手实现了。
加上这一段后,考虑从 b b b位置开始向左延伸,能延伸到的最大值就是答案,这个可以统计一个后缀和,然后求 [ a , b ] [a,b] [a,b]区间内的最大值即可。
区间 [ c , d ] [c,d] [c,d]类似,就是从 c c c位置开始向右延伸,能延伸到的最大值就是答案,这个统计前缀和,然后求 [ c , d ] [c,d] [c,d]区间内的最大值即可。
因为数据比较水 (明明是因为我太懒了),所以求区间最大子段和那个部分我就打了个
O
(
n
)
O(n)
O(n)暴力代替。。
代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 10010
int t,n,m;
int a[maxn],pre[maxn],suf[maxn];
struct node{
int l,r,z,maxpre,maxsuf;//z:区间和,maxpre:最大的前缀和,maxsuf:最大的后缀和
node *zuo,*you;
node():l(0),r(0),z(0),maxpre(0),maxsuf(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);
z=zuo->z+you->z;
maxpre=max(zuo->maxpre,you->maxpre);
maxsuf=max(zuo->maxsuf,you->maxsuf);
}
else z=a[x],maxpre=pre[x],maxsuf=suf[x];
}
int getsum(int x,int y)//求区间和,下面这三个函数实现都差不多,不用特别仔细看
{
if(x==l&&y==r)return z;
int mid=l+r>>1;
if(y<=mid)return zuo->getsum(x,y);
else if(x>=mid+1)return you->getsum(x,y);
else return zuo->getsum(x,mid)+you->getsum(mid+1,y);
}
int getmaxpre(int x,int y)//求区间最大前缀
{
if(x==l&&y==r)return maxpre;
int mid=l+r>>1;
if(y<=mid)return zuo->getmaxpre(x,y);
else if(x>=mid+1)return you->getmaxpre(x,y);
else return max(zuo->getmaxpre(x,mid),you->getmaxpre(mid+1,y));
}
int getmaxsuf(int x,int y)//求区间最大后缀
{
if(x==l&&y==r)return maxsuf;
int mid=l+r>>1;
if(y<=mid)return zuo->getmaxsuf(x,y);
else if(x>=mid+1)return you->getmaxsuf(x,y);
else return max(zuo->getmaxsuf(x,mid),you->getmaxsuf(mid+1,y));
}
void del()//因为多组数据,所以造出来的树用完了就随手删掉,虽然都不一定能节省空间。。
{
if(zuo!=NULL)zuo->del();
if(you!=NULL)you->del();
delete this;
}
};
node *root;
int work1(int x,int y,int xx,int yy)//work1就是上面的ans1
{
int tot=0;
if(xx-y>1)tot+=root->getsum(y+1,xx-1);//加上必选的中间那一段
tot+=root->getmaxsuf(x,y)-suf[y+1];//加上左边的最大后缀,记得要减掉suf[y+1],
tot+=root->getmaxpre(xx,yy)-pre[xx-1];//加上右边的最大前缀
return tot;
}
int work2(int x,int y)//同理,这个就是ans2
{//别谴责我打了个大暴力
int ans=-999999999,tot=0;//虽然是暴力,但还是有需要注意的地方——ans初值要设成一个很小的负数,原因自行理解
for(int i=x;i<=y;i++)
{
tot+=a[i];
if(tot>ans)ans=tot;
if(tot<0)tot=0;
}
return ans;
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
pre[0]=suf[n+1]=0;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),pre[i]=pre[i-1]+a[i];
for(int i=n;i>=1;i--)
suf[i]=suf[i+1]+a[i];
root=new node();
root->build(1,n);
scanf("%d",&m);
for(int i=1;i<=m;i++)//大力讨论
{
int x,y,xx,yy;
scanf("%d %d %d %d",&x,&y,&xx,&yy);
if(x>xx||(x==xx&&y<yy))swap(x,xx),swap(y,yy);//这个swap不能丢,原因见上面对x,y,xx,yy的定义
if(y<xx)printf("%d\n",work1(x,y,xx,yy));//无交集
if(x<xx&&y>=xx&&y<yy)printf("%d\n",max(work1(x,xx-1,xx,yy),max(work1(x,y,y+1,yy),work2(xx,y))));
//有交集
if(x<=xx&&y>=yy)//[x,y]包含了[xx,yy]
{
if(x==xx&&y==yy)printf("%d\n",work2(xx,yy));//两个端点都重合
else if(x==xx&&y!=yy)printf("%d\n",max(work2(xx,yy),work1(xx,yy,yy+1,y)));//只有左端点重合
else if(x!=xx&&y==yy)printf("%d\n",max(work1(x,xx-1,xx,yy),work2(xx,yy)));//只有右端点重合
else printf("%d\n",max(work1(x,xx-1,xx,yy),max(work2(xx,yy),work1(xx,yy,yy+1,y))));//两端都不重合
}
}
root->del();//删掉这棵树
}
}