【线段树】Fafa and Array

本文介绍了一种在序列上进行区间加法操作并查询修改后序列绝对差和最大值的算法。通过细致分析不同元素间的关系,实现了快速更新和查询。文章提供了完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:

给出一个长为N的序列,需要支持两种操作:
1、求出在序列中某个位置加上x后,使得

i=1i<n|ai+1ai|∑i=1i<n|ai+1−ai|
尽量大,并求出最大值。
2、将区间al,al+1,al+2aral,al+1,al+2……ar均加上x

分析:

这道题是用最优性来满足正确性的典型。

首先,我们考虑根据趋势,分为三种情况:
1.ai1aiai+11.ai−1≤ai≤ai+11.ai+1aiai11.ai+1≤ai≤ai−1
这种情况情况下,
如果max(ai1,ai+1)aixmax(ai−1,ai+1)−ai≥x,那么在这个位置加x是不会对答案有影响的。
如果不然,那么答案就会加上:x+x2×(max(ai1,ai+1)ai)x+x−2×(max(ai−1,ai+1)−ai)在这种情况下,答案一定会得到正影响。

2.ai1aiai+12.ai−1≥ai≤ai+1
在这种情况下,如果要在这个点加x,是有可能为负数影响的,我们分析一下:
(ai1ai+1)(我们假定ai−1≤ai+1)

如果ai+xai1,ai+1ai+x≤ai−1,ai+1
那么在这里加x,影响是2×x−2×x,为负

如果ai1ai+xai+1ai−1≤ai+x≤ai+1
在这里加x,影响是:
xx+aiai+1(ai+1ai)=2×aiai+1x−(x+ai−ai+1−(ai+1−ai))=2×(ai−ai+1)也为负。

如果ai+xai1,ai+1ai+x≥ai−1,ai+1
那么在这里加x,影响是2×x2×(ai+1ai)2×(ai1ai)2×x−2×(ai+1−ai)−2×(ai−1−ai)正负均可能。

3.ai1,ai+1ai3.ai−1,ai+1≤ai
在这种情况下,在这个点加x,那么影响为2×x2×x,始终为正且最大。

仔细观察我们可能为正的三个式子:
1. 2×x2×(max(ai1,ai+1)ai)1. 2×x−2×(max(ai−1,ai+1)−ai)
2. 2×x2×(ai+1ai)2×(ai1ai)2. 2×x−2×(ai+1−ai)−2×(ai−1−ai)
3. 2×x3. 2×x
很容易总结出,这三个式子都可以表示为:
2×xmax(0,ai1ai)max(0,ai+1ai)2×x−max(0,ai−1−ai)−max(0,ai+1−ai)
这样一来,我们可以通过维护max(0,ai1ai)+max(0,ai+1ai)max(0,ai−1−ai)+max(0,ai+1−ai)的最小值,来得到对答案的影响。

有的小朋友就会问了,那么影响为负的情况怎么办呢?那两种式子不能表示为这种形式啊?
其实很容易发现,2类情况不可能充满一个大于1的区间,即:一个大于1的区间不可能全为波谷。
又因为另外两种情况答案均不为负,所以我们可以保证在大于1的区间内,一定不会造成负影响。(这里就是我个人认为这道题最巧妙的一点,虽然其本身很显然,但这种用最优性来保证正确性的思考方式很难得一见)

那么我们只需要对区间大小分类讨论:若区间大小为1,就只能在那个点加x,手动得到答案。
如区间大小不为1,就可以按照我们维护的值来求答案。

差点忘了还有修改操作:
很容易发现,其实如果在一个区间内均加上一个值,那么区间内部的差是不会影响的,因此我们只需要考虑两个端点即可,说白了这里的修改其实只是单点修改。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define SF scanf
#define PF printf
#define MAXN 100010
#define INF 1e18
using namespace std;
long long tree[MAXN*4],ans;
int n,m;
long long a[MAXN];
long long absx(long long x){
    return x>0?x:-x;
}
void build(int id,int l,int r){
    if(l==r){
        ans+=absx(a[l]);
        if(l!=1)
            tree[id]=max(0ll,-a[l-1])+max(0ll,a[l]);
        return ;
    }
    int mid=(l+r)>>1;
    build(id<<1,l,mid);
    build(id<<1|1,mid+1,r);
    tree[id]=min(tree[id<<1],tree[id<<1|1]);
    //PF("[%d %d %lld]",l,r,tree[id]);
}
void add(int id,int l,int r,int pos,long long x){
    if(l==r){
        ans-=absx(a[l]);
        a[l]+=x;
        ans+=absx(a[l]);
        //PF("[%lld]",ans);
        if(pos>1)
            tree[id]=max(0ll,-a[l-1])+max(0ll,a[l]);
        if(x!=0&&pos<n-1)
            add(1,1,n-1,pos+1,0);
        return ;
    }
    int mid=(l+r)>>1;
    if(pos<=mid)
        add(id<<1,l,mid,pos,x);
    else
        add(id<<1|1,mid+1,r,pos,x);
    tree[id]=min(tree[id<<1],tree[id<<1|1]);
}
long long query(int id,int l,int r,int l1,int r1){
    if(l>=l1&&r<=r1)
        return tree[id];
    int mid=(l+r)>>1;
    long long res=INF;
    if(l1<=mid)
        res=min(res,query(id<<1,l,mid,l1,r1));
    if(r1>mid)
        res=min(res,query(id<<1|1,mid+1,r,l1,r1));
    return res;
}
int main(){
    int tag,l,r;
    long long x;
    SF("%d",&n);
    for(int i=1;i<=n;i++)
        SF("%d",&a[i]);
    for(int i=1;i<n;i++)
        a[i]=a[i+1]-a[i];
    build(1,1,n-1);
    SF("%d",&m);
    for(int i=1;i<=m;i++){
        SF("%d%d%d%lld",&tag,&l,&r,&x);
        if(tag==1){
            if(l==r){
                if(l==1)
                    PF("%lld\n",ans-absx(a[1])+absx(a[1]-x));
                else if(l==n)
                    PF("%lld\n",ans-absx(a[n-1])+absx(a[n-1]+x));
                else
                    PF("%lld\n",ans-absx(a[l-1])+absx(a[l-1]+x)-absx(a[l])+absx(a[l]-x));
            }
            else{
                long long res=max(0ll,2*x-2*query(1,1,n-1,max(2,l),r));
                //PF("[%lld]",2*x-2*query(1,1,n-1,max(2,l),r));
                if(l==1)
                    res=max(res,-absx(a[1])+absx(a[1]-x));
                if(r==n)
                    res=max(res,-absx(a[n-1])+absx(a[n-1]+x));
                PF("%lld\n",ans+res);
            }
        }
        else{
            if(l!=1)
                add(1,1,n-1,l-1,x);
            if(r!=n)
                add(1,1,n-1,r,-x);
        }
        //PF("[%lld]",ans);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值