你能回答这些问题吗

题目描述

给定长度为N的数列A,以及M条指令,每条指令可能是以下两种之一:
1、“1 x y”,查询区间 [x,y] 中的最大连续子段和,即 maxx≤l≤r≤y{∑ri=lA[i]}。
2、“2 x y”,把 A[x] 改成 y。
对于每个查询指令,输出一个整数表示答案。

输入格式

第一行两个整数N,M。
第二行N个整数A[i]。
接下来M行每行3个整数k,x,y,k=1表示查询(此时如果x>y,请交换x,y),k=2表示修改。

输出格式

对于每个查询指令输出一个整数表示答案。
每个答案占一行。

数据范围

N≤500000,M≤100000

输入样例:
5 3
1 2 -3 4 5
1 2 3
2 2 -1
1 3 2
输出样例:
2
-1

题目分析

这个题每次查询的是[l,r]中的最大子段和。那么每一段中我们也就要维护一个区间的最大子段和tmax。
但是我们可以发现:只依靠一个区间的左区间的tmax和右区间的tmax并不能够求出这个区间的tmax。
一个区间的最大子段和有三种可能的情况,1.这个区间的最大子段和完全在该区间的左区间内。2.这个区间的最大子段和完全在该区间的右区间内。3.这个区间的最大子段和占了左区间的后半部分和右区间的前半部分。因此我们还需要添加两个辅助信息lmax(该区间的最大前缀和)和rmax(这个区间的最大后缀和)。
那么一个区间的最大前缀和和最大后缀和怎么算呢?以前缀和为例:这个区间的最大前缀和包含两种情况:1.这个区间的最大前缀和小于一半,那么等于这个区间的左区间的最大前缀和。2.这个区间的最大前缀和大于一半,那么等于这个区间左区间的和+右区间的最大前缀和。
后缀和与前缀和求法相同,等于这个区间的右区间的最大后缀和或者这个区间右区间的和+左区间的最大后缀和。
那么,我们还需要添加一个辅助信息sum(记录这个区间的和)而它的求法即为左右两个区间的sum相加即可。

这样,所有的辅助信息及其求法就都确定了。用线段树求解即可。
tmax=(左右区间的最大子段和)以及(左区间的最大后缀和+右区间的最大前缀和)的最大值
lmax=(左区间的最大前缀和)以及(左区间的和+右区间的最大前缀和)的最大值
rmax=(右区间的最大后缀和)以及(右区间的和+左区间的最大后缀和)的最大值
sum=左右区间的sum的和

代码如下
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=5e5+5;
struct Node{
    int l,r;
    int sum,lmax,rmax,tmax;		//几个辅助信息
}tr[N*4];
int w[N];
void pushup(Node &u,Node &l,Node &r)	//通过子段的信息求出u段的全部辅助信息
{
    u.sum=l.sum+r.sum;					//这些的求法前面都讲了
    u.lmax=max(l.lmax,l.sum+r.lmax);
    u.rmax=max(r.rmax,r.sum+l.rmax);
    u.tmax=max(max(l.tmax,r.tmax),l.rmax+r.lmax);
}
void pushup(int u)					//包装一下,后面调用时写的舒服些
{
    pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void build(int u,int l,int r)		//建树
{
    if(l==r) tr[u]={l,r,w[r],w[r],w[r],w[r]};//l==r,u段中只有一个数,所以所有的辅助信息的值都是w[r]
    else
    {
        tr[u]={l,r};
        int mid=l+r>>1;
        build(u<<1,l,mid),build(u<<1|1,mid+1,r);
        pushup(u);
    }
}
void modify(int u,int x,int v)		//把x位置的值修改为v
{
    if(tr[u].l==x&&tr[u].r==x) tr[u]={x,x,v,v,v,v};	//当u段只有x一个数时,所有辅助信息都为v
    else
    {	//查看x位于那一段中,则修改哪一段
        int mid=tr[u].l+tr[u].r>>1;
        if(x<=mid) modify(u<<1,x,v);
        else modify(u<<1|1,x,v);
        pushup(u);				//修改后要更新u段的辅助信息
    }
}
Node query(int u,int l,int r)	//查询[l,r]段内的信息
{
    if(l<=tr[u].l&&r>=tr[u].r) return tr[u];	//如果u段在[l,r]内直接返回
    else
    {
        int mid=tr[u].l+tr[u].r>>1;
        if(mid>=r) return query(u<<1,l,r);		//mid>=r,说明u段的左区间部分在[l,r]内
        else if(mid<l) return query(u<<1|1,l,r);//mid<l,说明u段的右区间部分在[l,r]内
        else 	//否则说明u段的左右区间都有部分在[l,r]内
        {
            Node left=query(u<<1,l,r);
            Node right=query(u<<1|1,l,r);
            Node res;
            pushup(res,left,right);
            return res;
        }
    }
}
int main()
{
    int n,m;
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&w[i]);
    
    build(1,1,n);		//建树,范围为1-n
    while(m--)
    {
        int k,x,y;
        scanf("%d %d %d",&k,&x,&y);
        if(k==1)
        {
            if(x>y) swap(x,y);		//保证y>x
            printf("%d\n",query(1,x,y).tmax);	//直接查询[l,r]的tmax
        }
        else modify(1,x,y);			//修改信息
    }
    return 0;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lwz_159

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值