GSS5 - Can you answer these queries V 题解

题目传送门

题目大意: 多组询问,求给定序列的最大子段和,并且这个子段的两端的选取范围给出。

题解

来看题解的应该有不少来看坑点的,那我先把坑点说出来:

按洛谷上的翻译,查询左端点在[x1,y1]之间,且右端点在[x2,y2]之间的最大子段和,但实际上,也可以是右端点在[x1,y1]之间,且左端点在[x2,y2]之间的最大子段和,也就是说只要两端在这两个区间内即可。


进入正题

这题需要大力讨论。

给定区间的情况,总的来说无非三种:

  1. 无交集:
    在这里插入图片描述
  2. 有交集
    在这里插入图片描述
  3. 一个包含另一个
    在这里插入图片描述

还需要注意的就是给定区间的两端重合的情况需要特判罢了。

事实上,这些情况都可以拆成两种相同的情况来求解:

  1. 无交集
    在这里插入图片描述
  2. 完全相同
    在这里插入图片描述

为了方便,我们设

  • 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 &lt; x x ∨ ( x = x x ∧ y &gt; y y ) x&lt;xx \lor (x=xx \land y&gt;yy ) x<xx(x=xxy>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,xx1,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,xx1,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,c1]这一段是必选的,所以答案先加上这一段,这个求区间和可以用前缀和来实现,但是下面的操作需要用线段树,所以这个操作我也用线段树顺手实现了。

加上这一段后,考虑从 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();//删掉这棵树
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值