SPOJ2916 GSS5-Can you answer these queries V【线段树】
题目解析
我们发现译者在题面里加了一句非常有趣的话:
但是不保证端点所在的区间不重合
嘿嘿,\(get\)到了。
我们进行分类讨论,如果\(r1<l2\),那么事情就变得简单起来了:
\(ans=[l1,r1]\)的最大后缀+\(sum[r1+1,l2-1]\)+\([l2,r2]\)的最大前缀
然后那玩意儿用GSS1的方法维护就好了
而如果\(r1>l2\),也就是两个区间有交集,那么还会有更多种情况:
第一种:
\(ans=[l2,r1]\)的最大子段和
第二种:
左端点在\([l1,l2]\),右端点在\([l2,r2]\)
\(ans=[l1,l2]\)最大后缀\(+[l2,r2]\)最大前缀
第三种:
左端点在\([l1,r1]\),右端点在\([r1,r2]\)
\(ans=[l1,r1]\)最大后缀\(+[r1,r2]\)最大前缀
以上三种情况都可以用GSS1的方法维护,三个方案取\(max\)即可。
但是,
这道题真没想的那么简单,
主要是它有很多边界上的细节容易出错,而且不好改。
首先,在\(r1<l2\)的情况里,有一个\(sum[r1+1,l2-1]\),这里可能在查询时出现\(l>r\)的情况,要特判一下。
其次,我们前面没有讨论\(r1==l2\)的情况。
我以为随便丢在哪种情况里都可以来着,但是如果丢在第一种情况,边界就会被算两次,所以最好丢到第二种情况去。
如果改成\(ans=[l1,r1-1]\)的最大后缀+\(sum[r1,l2]\)+\([l2+1,r2]\)的最大前缀,那就意味着左右两个区间的端点必须是\(r1-1\)和\(l2+1\),但不一定啊,它可以不包括\(r1-1\)和\(l2+1\),所以这么写还要判断一下\([l1,r1-1]\)的最大后缀和\([l2+1,r2]\)的最大前缀是不是负数,如果是负数就不加(好麻烦
再然后,就是第二个大类里面的,我以为下面的边界可以归在左右随便哪个区间里就可以了,毕竟边界点总会在答案里。
但没有想到的是,这道题它不能随便,虽然边界点总会在答案里,但是不同的写法对边界点左右两边是否一定要在区间里产生影响(类似于刚才的那个讨论)。这么说可能有点抽象,我具体写在代码注释里了。
►Code View
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
#define LL long long
#define N 10005
#define DEL 100000
#define INF 0x3f3f3f3f
#define MOD 998244353
LL rd()
{
LL x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48); c=getchar();}
return f*x;
}
int n;
struct node{
int sum,mx,mxl,mxr;
}tree[N<<2];
void PushUp(int i)
{
tree[i].sum=tree[i<<1].sum+tree[i<<1|1].sum;
tree[i].mx=max(max(tree[i<<1].mx,tree[i<<1|1].mx),tree[i<<1].mxr+tree[i<<1|1].mxl);
tree[i].mxl=max(tree[i<<1].mxl,tree[i<<1].sum+tree[i<<1|1].mxl);
tree[i].mxr=max(tree[i<<1|1].mxr,tree[i<<1|1].sum+tree[i<<1].mxr);
}
void Build(int i,int l,int r)
{
if(l==r)
{
tree[i].sum=tree[i].mx=tree[i].mxl=tree[i].mxr=rd();
return ;
}
int mid=(l+r)>>1;
Build(i<<1,l,mid);
Build(i<<1|1,mid+1,r);
PushUp(i);
}
node Query(int i,int l,int r,int ql,int qr)
{
if(ql>qr)
{//边界+1-1之后可能会出现这种情况 需要特判
node res;
res.sum=res.mx=res.mxl=res.mxr=0;
return res;
}
if(ql<=l&&r<=qr) return tree[i];
int mid=(l+r)>>1;
if(qr<=mid) return Query(i<<1,l,mid,ql,qr);
else if(ql>mid) return Query(i<<1|1,mid+1,r,ql,qr);
else
{
node x=Query(i<<1,l,mid,ql,qr),y=Query(i<<1|1,mid+1,r,ql,qr),res;
res.sum=x.sum+y.sum;
res.mx=max(max(x.mx,y.mx),x.mxr+y.mxl);
res.mxl=max(x.mxl,x.sum+y.mxl);
res.mxr=max(y.mxr,y.sum+x.mxr);
return res;
}
}
int main()
{
int T=rd();
while(T--)
{
n=rd();
Build(1,1,n);
int Q=rd();
while(Q--)
{
int l1=rd(),r1=rd(),l2=rd(),r2=rd();
if(r1<l2)
{//这里不能取等 不然边界会被算2次
node x=Query(1,1,n,l1,r1),y=Query(1,1,n,l2,r2),z=Query(1,1,n,r1+1,l2-1)/*注意边界+1-1*/;
printf("%d\n",x.mxr+z.sum+y.mxl);
}
else
{
int res=Query(1,1,n,l2,r1).mx;
node x=Query(1,1,n,l1,l2-1),y=Query(1,1,n,l2,r2);
//啊 这里之前写的 x=Query(1,1,n,l1,l2),y=Query(1,1,n,l2+1,r2) 就过不了
//那样写的话 右端点恰好在l2上的情况就无法被计算到
//而左端点恰好在l2上的情况可以在第一种和第三种情况中被计算
//同理 下面也必须写成r1和r1+1 否则左端点恰好在r1的情况就无法被计算到
res=max(res,x.mxr+y.mxl);
x=Query(1,1,n,l1,r1),y=Query(1,1,n,r1+1,r2);
res=max(res,x.mxr+y.mxl);
printf("%d\n",res);
}
}
}
return 0;
}