SPOJ GSS1 Can you answer these queries I (线段树求区间最大连续和)

题目大意

给定一个数列,并给出一些询问的区间,求出询问区间内的最大连续区间和。

解题思路

直接上线段树,但是区间合并的时候遇到了一些困难。

首先我们考虑,想要达到 logn 级别的访问时间,对于线段树的每个节点中必须保存有该节点所表示区间内的最大连续区间和 mmax 。
然后我们考虑区间合并时最大值的取值情况:

1、等于左子结点中的区间最大值。
2、等于右子结点中的区间最大值。
3、最大连续区间横跨左子区间和右子区间。

在情况(1,2)和情况 3 取最大值的时候会遇到一些问题, 所以对于每个结点我们需要引入新的变量来存储更多的信息:

1、sum 表示该结点区间和。
2、suml 表示该结点包含最左端元素的最大连续区间和。
3、sumr 表示该结点包含最右端元素的最大连续区间和。

因此可得 mmax 的递推方程为

p[root].mmax=max(max(p[root<<1].mmax,p[root<<1|1].mmax),(p[root<<1].sumr+p[root<<1|1].suml));(root为当前的根节点)

suml 和 sumr 的递推方程为

p[root].suml=max(p[root<<1].suml,p[root<<1].sum+p[root<<1|1].suml);
p[root].sumr=max(p[root<<1|1].sumr,p[root<<1].sumr+p[root<<1|1].sum);

查询过程:

定义查询区间为 [ l , r ] , mid 为结点区间 [ left , right ] 中点
1、若查询区间位于 mid 左,则返回左子区间的查询结果。
2、若查询区间位于 mid 右,则返回右子区间的查询结果。
3、横跨左右子区间,则分别查询 [ l , mid ] 和 [ mid+1 , r ] 后按照上述规则合并。

附上代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAX 50000+50
using namespace std;
int num[MAX];
struct node{
    int left,right;
    int mmax,sum,suml,sumr;
}p[MAX<<2];
int a[MAX];
void build(int id,int l,int r)
{
    p[id].left=l;
    p[id].right=r;
    if(l==r)
    {
        p[id].sum=a[l];
        p[id].mmax=a[l];
        p[id].suml=a[l];
        p[id].sumr=a[l];
    }
    else
    {
        int mid=(l+r)/2;
        build(2*id,l,mid);
        build(2*id+1,mid+1,r);
        p[id].sum=p[2*id].sum+p[2*id+1].sum;
        p[id].mmax=max(max(p[2*id].mmax,p[2*id+1].mmax),(p[2*id].sumr+p[2*id+1].suml));
        p[id].suml=max(p[2*id].suml,p[2*id].sum+p[2*id+1].suml);
        p[id].sumr=max(p[2*id+1].sumr,p[2*id].sumr+p[2*id+1].sum);
    }
}
node query_sum(int id,int l,int r)
{
    if(p[id].left==l&&p[id].right==r)
        return p[id];
    else
    {
        node tmp,k1,k2;
        int mid=(p[id].left+p[id].right)/2;
        if(r<=mid)
            return query_sum(2*id,l,r);
        if(l>mid)
            return query_sum(2*id+1,l,r);
        k1=query_sum(2*id,l,mid);
        k2=query_sum(2*id+1,mid+1,r);
        tmp.sum=k1.sum+k2.sum;
        tmp.suml=max(k1.suml,k1.sum+k2.suml);
        tmp.sumr=max(k2.sumr,k1.sumr+k2.sum);
        tmp.mmax=max(max(k1.mmax,k2.mmax),k1.sumr+k2.suml);
        return tmp;
    }
}
int main()
{
    int n,q;
    while(~scanf("%d",&n)){
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        build(1,1,n);
        scanf("%d",&q);
        while(q--){
            int left,right;
            scanf("%d%d",&left,&right);
            node ans=query_sum(1,left,right);
            printf("%d\n",ans.mmax);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值