UValive 3938——动态最大连续和——线段树

题目链接:https://vjudge.net/contest/238229#problem/E

思路:说实话,千万句题解思路不如几行关键代码,靠大神的代码才让我真正弄懂该怎么做T_T   详见:大佬博客

我觉得这题应该注意的地方:

1.线段树节点应该保存的东西:最大连续和的起点和终点,可以用pair保存;最大前缀和的终点prer;最大后缀和的起点sufl。

那么,我们在构建的时候,从小区间回归到大区间,要确定的就是最大连续和的起点和终点的位置,有三种情况:

(1)比较之后,起点和终点都在左边或右边,则大区间.sub=max(左小区间.sub,又小区间.sub)

(2)起点在左边,终点在右边,那么也就只有可能从左边的最大后缀和起点开始,到右边的最大前缀和终点结束,这一定是第三

种情况最大的合并连续和了。

以上这两点正是合并两个子区间的代码精髓所在,具体见代码。(注释写的很详细了,主要怕我自己看不懂

2.保存一个数组前缀和,用于计算连续区间长度的大小。

还有许多,都在注释中了,这题对我这蒟蒻不友好啊......

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

const int maxn=500000+10;
typedef long long ll;
typedef pair<int,int> par;
ll a[maxn];
int n,q,temp,l,r;
ll sum[maxn];//前缀和,要求一段区间的值就是sum[r]-sum[l-1]

struct node
{
    int l,r,prer,sufl;//左右节点,最大前缀和终点,最大后缀和起点
    par sub;//结果
}nodes[maxn<<2];
ll cal(par x)
{
    return sum[x.second]-sum[x.first-1];//求区间和
}
//比较两个区间和大小
par cmp(par x,par y)
{
    ll a=cal(x);
    ll b=cal(y);
    if(a!=b) return a>b?x:y;
    return x<y?x:y;
}
//合并
node com(node x,node y)
{
    node res;
    res.l=x.l;
    res.r=y.r;
    res.prer=cmp(par(x.l,x.prer),par(x.l,y.prer)).second;//新节点的最大前缀和终点,画个图就能理解
    res.sufl=cmp(par(y.sufl,y.r),par(x.sufl,y.r)).first;//新节点的最大后缀和
    //答案的区间只会有三种情况,要么左边,要么右边,要么中间。
    res.sub=cmp(par(x.sufl,y.prer),cmp(x.sub,y.sub));
    return res;
}
void build(int l,int r,int root)
{
    if(l==r)
    {
        //根节点
        nodes[root].l=nodes[root].r=nodes[root].prer=nodes[root].sufl=l;
        nodes[root].sub=par(l,l);
        return;
    }
    int m=(l+r)/2;
    build(l,m,root*2);//左节点
    build(m+1,r,root*2+1);//右节点
    nodes[root]=com(nodes[root*2],nodes[root*2+1]);//合并
}
node query(int l,int r,int root)
{
    if(nodes[root].l>=l&&nodes[root].r<=r)return nodes[root];
    int m=(nodes[root].l+nodes[root].r)/2;
    node res;
    if(l<=m&&r>m)res=com(query(l,r,root*2),query(l,r,root*2+1));//查询区间包含左右合集
    else if (l<=m)  res=query(l,r,root*2); //l<=m且r<=m,说明只在左边
    else res=query(l,r,root*2+1);   //l>m且r>m说明只在右边
    return res;
}
int main()
{
    int index=1;
    while(scanf("%d%d",&n,&q)!=EOF)
    {
        memset(sum,0,sizeof(sum));
        for(int i=1;i<=n;i++)
        {
            scanf("%lld",&a[i]);
            sum[i]=a[i]+sum[i-1];
        }
        build(1,n,1);//构建线段树
        printf("Case %d:\n",index++);
        for(int i=1;i<=q;++i)
        {
            scanf("%d%d",&l,&r);
            par ans=query(l,r,1).sub;
            printf("%d %d\n",ans.first,ans.second);
        }
    }
    return 0;
}

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值