【SPOJ2916 GSS5】Can you answer these queries V——杨子曰题目

【SPOJ GSS5】Can you answer these queries V——杨子曰题目

题目描述
You are given a sequence A[1], A[2], …, A[N] . ( |A[i]| <= 10000 , 1 <= N <= 10000 ). A query is defined as follows: Query(x1,y1,x2,y2) = Max { A[i]+A[i+1]+…+A[j] ; x1 <= i <= y1 , x2 <= j <= y2 and x1 <= x2 , y1 <= y2 }. Given M queries (1 <= M <= 10000), your program must output the results of these queries.

输入格式:
The first line of the input consist of the number of tests cases <= 5. Each case consist of the integer N and the sequence A. Then the integer M. M lines follow, contains 4 numbers x1, y1, x2 y2.

输出格式:
Your program should output the results of the M queries for each test case, one query per line.

输入样例:

2
6 3 -2 1 -4 5 2
2
1 1 2 3
1 3 2 5
1 1
1
1 1 1 1

输出样例:

2
3
1

一道毒瘤数据结构题:给你一个序列,对于每一个询问的 l 1 , r 1 , l 2 , r 2 l_1,r_1,l_2,r_2 l1,r1,l2,r2,输出左端点在 [ l 1 , r 1 ] [l_1,r_1] [l1,r1],右端点在 [ l 2 , r 2 ] [l_2,r_2] [l2,r2]的子段中最大的和


请先用线段树解决这道题:【SPOJ GSS3】Can you answer these queries III


假设你已经看了上面那篇题解,我们来讲一讲这道题的思路:

首先我们在线段树上要维护的东西和上面那道题是完全一样的,在这里罗列一下:

  • ms:给区间的最大字序列和
  • ls:包括左端点的区间最大子序列和
  • rs:包括右端点的区间最大子序列和
  • sum:区间的和

显然要分类讨论了,那我们就来讨论一下(下文的区间[l,r]的端点不一定是准确的,在代码中需要+1或者-1,保证在临界位置不会重复计算):

首先,两个区间不相交:
在这里插入图片描述
然后你有没有发现满足要去的子序列一定包含中间这一段:
在这里插入图片描述
So,我们要把区间 ( r 1 , l 2 ) (r_1,l_2) (r1,l2)这一段的sum求出来,然后我们还要考虑两边,比如答案长这样:
在这里插入图片描述
那么蓝色和绿色分别是神马捏?应该就是区间 [ l 1 , r 1 ] [l_1,r_1] [l1,r1]的rs和区间 [ l 2 , r 2 ] [l_2,r_2] [l2,r2]的ls,OK,第一种情况就被我们解决了


然后我们呢再来讨论两个区间相交的情况:
在这里插入图片描述

我们像这样分成三段:
在这里插入图片描述
我们又要分三种小情况来考虑:

  1. 答案横跨了一段(只能是中间那一段)
    在这里插入图片描述
    这很简单,就是区间 [ l 2 , r 1 ] [l_2,r_1] [l2,r1]的ms

  2. 答案横跨两段(以靠左边为例)
    在这里插入图片描述
    这个是什么呢?
    在这里插入图片描述
    有没有发现黄色是区间 [ l 1 , l 2 ] [l_1,l_2] [l1,l2]的rs,而绿色部分则是区间 ( l 2 , r 1 ] ( 就 是 [ l 2 + 1 , r 1 ] ) (l_2,r_1](就是[l_2+1,r_1]) (l2,r1][l2+1,r1]的ls
    如果区间靠右边也是一样的

  3. 答案横跨3个区间
    在这里插入图片描述
    这可以怎么算捏?
    很多人可能会想到分成3段来算
    但其实我们只要这样就可以了:
    在这里插入图片描述
    黄色是区间 [ l 1 , l 2 ] [l_1,l_2] [l1,l2]的rs,而绿色部分则是区间 ( l 2 , r 2 ] ( 就 是 [ l 2 + 1 , r 2 ] ) (l_2,r_2](就是[l_2+1,r_2]) (l2,r2][l2+1,r2]的ls
    然后机智的你一定发现了第2种情况是可以这种情况何在一起算的,就是 [ l 1 , l 2 ] 的 r s + 区 间 ( l 2 , r 2 ] 的 l s [l_1,l_2]的rs+区间(l_2,r_2]的ls [l1,l2]rs+(l2,r2]ls

至此我们终于把所有的情况全部讨论完了

OK,完事


C++代码:

#include<bits/stdc++.h>
#define inf 2000000000
using namespace std;

const int maxn=10005;
int a[maxn],sum[maxn*4],ms[maxn*4],ls[maxn*4],rs[maxn*4];

void init(){
	for (int i=1;i<=40000;i++){
		sum[i]=ms[i]=ls[i]=rs[i]=-inf;
	}
}

void pushup(int nod){
    ms[nod]=max(max(ms[nod*2],ms[nod*2+1]),rs[nod*2]+ls[nod*2+1]);
    ls[nod]=max(ls[nod*2],sum[nod*2]+ls[nod*2+1]);
    rs[nod]=max(rs[nod*2+1],sum[nod*2+1]+rs[nod*2]);
    sum[nod]=sum[nod*2]+sum[nod*2+1];
}

void build(int l,int r,int nod){
    if (l==r){
        sum[nod]=ms[nod]=ls[nod]=rs[nod]=a[l];
        return;
    }
    int mid=(l+r)/2;
    build(l,mid,nod*2);
    build(mid+1,r,nod*2+1);
    pushup(nod);
}


void query(int l,int r,int ll,int rr,int nod,int &s,int &ans,int &ans_l,int &ans_r){
	if (ll>rr){
		s=ans=ans_l=ans_r=0;
		return; 
	}
    if (l==ll && r==rr){
        ans=ms[nod];
        ans_l=ls[nod];
        ans_r=rs[nod];
        s=sum[nod];
        return;
    }
    int mid=(l+r)/2;
    if (rr<=mid) query(l,mid,ll,rr,nod*2,s,ans,ans_l,ans_r);
    else if (ll>mid) query(mid+1,r,ll,rr,nod*2+1,s,ans,ans_l,ans_r);
    else{
        int l_ans,r_ans,l_ans_l,l_ans_r,r_ans_l,r_ans_r,l_s,r_s;
        query(l,mid,ll,mid,nod*2,l_s,l_ans,l_ans_l,l_ans_r);
        query(mid+1,r,mid+1,rr,nod*2+1,r_s,r_ans,r_ans_l,r_ans_r);
        ans=max(max(l_ans,r_ans),l_ans_r+r_ans_l);
        ans_l=max(l_ans_l,l_s+r_ans_l);
        ans_r=max(r_ans_r,r_s+l_ans_r);
        s=l_s+r_s;
    }
}//看不懂这些变量名的同学去看看我上面那篇GSS3的博客

int main(){
    int cas;
    scanf("%d",&cas);
    while(cas--){
    	init();
    	int n,m;
    	scanf("%d",&n);
    	for (int i=1;i<=n;i++){
    		scanf("%d",&a[i]);
		}
		build(1,n,1);
		scanf("%d",&m);
		while(m--){
			int l1,r1,l2,r2;
			scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
			int sum,ans,ansl,ansr,tmp[10];
			if (l1==l2 && r1==r2) query(1,n,l1,r1,1,sum,ans,ansl,ansr);//特判一下两个区间重复的情况
			else if (r1<l2){//两个区间没有相交
				query(1,n,l1,r1,1,tmp[1],tmp[2],tmp[3],ansr);
				query(1,n,l2,r2,1,tmp[1],tmp[2],ansl,tmp[3]);
				query(1,n,r1+1,l2-1,1,sum,tmp[1],tmp[2],tmp[3]);
				ans=ansr+sum+ansl;
			}
			else{//两个区间相交
				query(1,n,l2,r1,1,tmp[1],ans,tmp[2],tmp[3]);//第1种情况
				
				query(1,n,l1,l2,1,tmp[1],tmp[2],tmp[3],tmp[4]);
				query(1,n,l2+1,r2,1,tmp[5],tmp[6],tmp[7],tmp[8]);
				ans=max(ans,tmp[4]+max(0,tmp[7]));//第二种和第三种情况,靠左
				//由于(l2,r2]是左端点是开的,所以要和0取max
				query(1,n,l1,r1,1,tmp[1],tmp[2],tmp[3],tmp[4]);
				query(1,n,r1+1,r2,1,tmp[5],tmp[6],tmp[7],tmp[8]);
				ans=max(ans,tmp[4]+max(0,tmp[7]));//第二种和第三种情况,靠右
				//由于(r1,r2]是左端点是开的,所以要和0取max
			}
			printf("%d\n",ans);
		}
	}
    return 0;
}

参考:
https://www.luogu.org/blog/energy2002/solution-sp2916

于HG机房

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值