[清华集训2015]V

题目大意

给出一个序列a,现在有五种操作
1、把[l,r]每个ai加上x
2、把[l,r]每个ai变成x
3、把[l,r]每个ai变成max(ai-x,0)
4、询问目前ai的值
5、询问ai的历史最大值

线段树

我们考虑三种修改操作,可以发现它们可以用一种标记(a,b)表示
x经过(a,b)后会变成max(a+x,b)
那么三种操作分别对应
(x,-inf)
(-inf,x)
(-x,0)
现在考虑这种标记如何合并,x先经过(a,b)再经过(c,d)变成什么
max(max(a+x,b)+c,d)
max(a+x+c,b+c,d)
max(a+c+x,max(b+c,d))
因此(a,b)和(c,d)合并变成(a+c,max(b+c,d))
那么我们可以维护第四种操作即询问当前值。
现在,我们还需要维护历史最大值,因此我们设一个历史最大标记。
大概是个什么意思呢?考虑以下m次操作,分别打上了标记f1,f2,f3……fm。这里的标记我们看做一种函数,观察标记的形式发现这是一个常数函数加斜率为1的一次函数组成的。
为了方便,初始时也有标记f0=(0,-inf)
那么值x,初始为f0(x),然后变成f1(f0(x)),然后变成f2(f1(f0(x)))……
为了方便,我们继续设合并后的当前标记Fi=f0~i合并后的结果。
于是值x,初始为F0(x),然后变成F1(x),然后变成F2(x)……
我们就是要在F0~m(x)中求最大值,这就是历史最大值!即无论初值是什么,G(x)代表了初值x经过这m次标记后的历史最大值。
历史标记G就是F0~m取max后的结果,可以发现历史标记和当前标记是一样的形式!
考虑如何合并两个历史标记(a,b)和(c,d),那么就是变成(max(a,c),max(b,d))
接下来实现的时候,我们在线段树每个节点上维护当前标记以及历史标记,由于有了这两个标记,a数组可以一直不变,询问时直接找到其叶子节点对应的标记即可。
下传标记时,例如父亲的下传到一个儿子,那么父亲的历史标记要先与儿子的当前标记合并,然后再与儿子的历史标记合并。接着再直接用父亲的当前标记与儿子的当前标记合并即可。
注意,因为-inf的问题,我们需要保证标记每个值都不小于-inf。大概就是你不能加爆了。

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int maxn=500000+10;
const ll inf=100000000000000000;
struct dong{
    ll a,b,ma,mb;
    void operator +=(const dong &p){
        ma=max(ma,p.ma+a);
        mb=max(mb,max(b+p.ma,p.mb));
        a=max(a+p.a,-inf);
        b=max(b+p.a,p.b);
    }
} tree[maxn*4];
int a[maxn],leaf[maxn];
int i,j,k,l,r,x,t,n,m;
dong p;
int read(){
    int x=0,f=1;
    char ch=getchar();
    while (ch<'0'||ch>'9'){
        if (ch=='-') f=-1;
        ch=getchar();
    }
    while (ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
void build(int p,int l,int r){
    tree[p]=(dong){0,-inf,0,-inf};
    if (l==r){
        leaf[l]=p;
        return;
    };
    int mid=(l+r)/2;
    build(p*2,l,mid);
    build(p*2+1,mid+1,r);
}
void down(int p){
    tree[p*2]+=tree[p];
    tree[p*2+1]+=tree[p];
    tree[p]=(dong){0,-inf,0,-inf};
}
void change(int x,int l,int r,int a,int b){
    if (l==a&&r==b){
        tree[x]+=p;
        return;
    }
    down(x);
    int mid=(l+r)/2;
    if (b<=mid) change(x*2,l,mid,a,b);
    else if (a>mid) change(x*2+1,mid+1,r,a,b);
    else{
        change(x*2,l,mid,a,mid);
        change(x*2+1,mid+1,r,mid+1,b);
    }
}
void query(int p,int l,int r,int a){
    if (l==r) return;
    down(p);
    int mid=(l+r)/2;
    if (a<=mid) query(p*2,l,mid,a);else query(p*2+1,mid+1,r,a);
}
int main(){
    n=read();m=read();
    fo(i,1,n) a[i]=read();
    build(1,1,n);
    while (m--){
        t=read();
        if (t<=3){
            l=read();r=read();x=read();
            if (t==1) p=(dong){x,-inf,x,-inf};
            else if (t==2) p=(dong){-x,0,-x,0};
            else p=(dong){-inf,x,-inf,x};
            change(1,1,n,l,r);
        }
        else{
            x=read();
            query(1,1,n,x);
            p=tree[leaf[x]];
            if (t==4) printf("%lld\n",max((ll)a[x]+p.a,p.b));
            else printf("%lld\n",max((ll)a[x]+p.ma,p.mb));
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值