P4513 小白逛公园(线段树求区间最大子段和)

 题目链接:https://www.luogu.org/problem/P4513

题目背景

小新经常陪小白去公园玩,也就是所谓的遛狗啦…

题目描述

在小新家附近有一条“公园路”,路的一边从南到北依次排着nn个公园,小白早就看花了眼,自己也不清楚该去哪些公园玩了。

一开始,小白就根据公园的风景给每个公园打了分-.-。小新为了省事,每次遛狗的时候都会事先规定一个范围,小白只可以选择第aa个和第bb个公园之间(包括aa、bb两个公园)选择连续的一些公园玩。小白当然希望选出的公园的分数总和尽量高咯。同时,由于一些公园的景观会有所改变,所以,小白的打分也可能会有一些变化。

那么,就请你来帮小白选择公园吧。

输入格式

第一行,两个整数NN和MM,分别表示表示公园的数量和操作(遛狗或者改变打分)总数。
接下来NN行,每行一个整数,依次给出小白 开始时对公园的打分。
接下来MM行,每行三个整数。第一个整数KK,11或22。

  • K=1K=1表示,小新要带小白出去玩,接下来的两个整数aa和bb给出了选择公园的范围(1≤a,b≤N1a,bN)。测试数据可能会出现a>ba>b的情况,需要进行交换;
  • K=2K=2表示,小白改变了对某个公园的打分,接下来的两个整数pp和ss,表示小白对第pp个公园的打分变成了ss(1≤p≤N1pN)。
    其中,1≤N≤500 0001N500000,1≤M≤100 0001M100000,所有打分都是绝对值不超过10001000的整数。

输出格式

小白每出去玩一次,都对应输出一行,只包含一个整数,表示小白可以选出的公园得分和的最大值。

输入输出样例

输入 #1
5 3
1 2 -3 4 5
1 2 3
2 2 -1
1 2 3
输出 #1
2
-1
思路:
这是一道线段树的板子题,利用线段树维护区间最大子段和
这道题的题意是维护一个动态的带修改最大子段和。
我们发现这个最大子段和是可以通过一些维护更新由两个区间合并到一个区间的,所以可以用线段树维护。
具体怎样维护?
我们让tot[i]表示i这个节点代表区间的数的总和,
lm[i]表示i这个节点代表区间从左端点开始(必须包括左端点)的最大子段和,
rm[i]表示i这个节点代表区间从右端点开始(必须包括右端点)的最大子段和,
ans[i]表示i这个节点代表区间的最大子段和。这样我们就可以合并两个区间的答案了。 具体的更新维护代码如下。
void Push_up(int rt)
{
    int ll=node[rt].lc,rr=node[rt].rc;
    node[rt].tot=node[ll].tot+node[rr].tot;//更新总和
    //更新左端点开始的最大子段和。两种情况分别是不跨过mid和跨过mid
    node[rt].lm=max(node[ll].lm,node[ll].tot+node[rr].lm);
    //更新右端点开始的最大子段和。两种情况分别是不跨过mid和跨过mid
    node[rt].rm=max(node[rr].rm,node[rr].tot+node[ll].rm);
    //更新最大子段和的答案。分三种情况:最大子段只在mid左边,跨过mid,只在mid右边
    node[rt].ans=max(max(node[ll].ans,node[rr].ans),node[ll].rm+node[rr].lm);
}

看完整代码:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long LL;
const int maxn=5e5+5;
struct Node
{
    int l,r,lc,rc,lm,rm,ans,tot;
}node[maxn<<2];
int n,m,k,x,y,cnt=0,a[maxn];
void Push_up(int rt)
{
    int ll=node[rt].lc,rr=node[rt].rc;
    node[rt].tot=node[ll].tot+node[rr].tot;//更新总和
    //更新左端点开始的最大子段和。两种情况分别是不跨过mid和跨过mid
    node[rt].lm=max(node[ll].lm,node[ll].tot+node[rr].lm);
    //更新右端点开始的最大子段和。两种情况分别是不跨过mid和跨过mid
    node[rt].rm=max(node[rr].rm,node[rr].tot+node[ll].rm);
    //更新最大子段和的答案。分三种情况:最大子段只在mid左边,跨过mid,只在mid右边
    node[rt].ans=max(max(node[ll].ans,node[rr].ans),node[ll].rm+node[rr].lm);
}
void Build(int l,int r,int rt)
{
    node[rt].l=l;
    node[rt].r=r;
    if(l==r)
    {
        node[rt].lm=node[rt].rm=node[rt].tot=node[rt].ans=a[l];
        return ;
    }
    int mid=(l+r)>>1;
    node[rt].lc=rt<<1;
    Build(l,mid,node[rt].lc);
    node[rt].rc=rt<<1|1;
    Build(mid+1,r,node[rt].rc);
    Push_up(rt);//合并答案
}
Node Query(int rt,int l,int r)
{
    int x=node[rt].l,y=node[rt].r;
    if(l<=x&&y<=r) return node[rt];//当前节点在询问区间当中 直接返回答案
    int mid=(x+y)>>1,ll=node[rt].lc,rr=node[rt].rc;
    if(r<=mid) //全部在左区间
        return Query(ll,l,r);
    else if(l>mid) //全部在右区间
        return Query(rr,l,r);
    else //询问区间跨过mid时要合并一下答案 思路与Pushup函数中的差不多
    {
        Node t,t1=Query(ll,l,r),t2=Query(rr,l,r);
        t.lm=max(t1.lm,t1.tot+t2.lm);
        t.rm=max(t2.rm,t2.tot+t1.rm);
        t.ans=max(max(t1.ans,t2.ans),t1.rm+t2.lm);
        return t;
    }
}
void Update(int rt,int to,int num)
{
    int x=node[rt].l,y=node[rt].r;
    if(x==y)
    {
        node[rt].lm=node[rt].rm=node[rt].tot=node[rt].ans=num;//修改
        return ;
    }
    int mid=(x+y)>>1;
    if(to<=mid) Update(node[rt].lc,to,num);
    else Update(node[rt].rc,to,num);
    Push_up(rt);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    Build(1,n,1);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&k,&x,&y);
        if(k==1)
        {
            if(x>y) swap(x,y);
            printf("%d\n",Query(1,x,y).ans);
        }
        else Update(1,x,y);
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/caijiaming/p/11440112.html
今日推荐