bzoj3064/洛谷P4314 CPU监控 线段树

题目分析

看到这道题,你一定想到要新开一款和历史有关的标记……
但是这个标记很不好合并……
所以就有一种独特的打标记法……
就是一个标记tag,它是一个数对 (a,b) ( a , b ) ,表示这个区间内所有值都要变成 max(v+a,b) m a x ( v + a , b ) ,这样如果是区间加,就打上 (a,inf) ( a , − i n f ) 标记,是区间减就打上 (inf,b) ( − i n f , b ) 标记……
这个标记的合并呢……比如说当前区间原有标记 (a1,b1) ( a 1 , b 1 ) ,还要打上新标记 (a2,b2) ( a 2 , b 2 ) ,那么区间每一个值就变成 max(max(v+a1,b1)+a2,b2) m a x ( m a x ( v + a 1 , b 1 ) + a 2 , b 2 ) ,也就是把标记变成 (a1+a2,max(b1+a2,b2)) ( a 1 + a 2 , m a x ( b 1 + a 2 , b 2 ) ) …..
然后新开一款历史版标记,记为ht,表示当前这个区间最值,还未被标记tag改变,可以用的把其更新到最大的历史标记……
现在我们重载运算符,加法就是标记合并,取max就是a和b两位都取max合成的一个新标记,则pushdown操作如下:

void work(int i,node t1,node t2) {
    ht[i]=max(ht[i],tag[i]+t2);//加在当前这些v上的历史标记们,增加了tag[i]+t2这个标记
    hv[i]=max(hv[i],t2.cal(v[i]));//用最优历史标记更新v[i]的结果加在hv[i]上
    tag[i]=tag[i]+t1,v[i]=t1.cal(v[i]);
}
void pd(int i) {
    work(i<<1,tag[i],ht[i]);
    work((i<<1)|1,tag[i],ht[i]);
    tag[i].clear(),ht[i].clear();
}

代码

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
    int q=0,w=1;char ch=' ';
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
    return q*w;
}
const int N=100005,inf=0x3f3f3f3f;
struct node{
    int a,b;
    void clear() {a=0,b=-inf;}
    int cal(int x) {return max(x+a,b);}
}tag[N<<2],ht[N<<2];
node operator + (node x,node y)
    {return (node){max(-inf,x.a+y.a),max(x.b+y.a,y.b)};}
node max(node x,node y) {return (node){max(x.a,y.a),max(x.b,y.b)};}
int v[N<<2],hv[N<<2],n,m,a[N];
void work(int i,node t1,node t2) {
    ht[i]=max(ht[i],tag[i]+t2);
    hv[i]=max(hv[i],t2.cal(v[i]));
    tag[i]=tag[i]+t1,v[i]=t1.cal(v[i]);
}
void up(int i) {
    v[i]=max(v[i<<1],v[(i<<1)|1]);
    hv[i]=max(hv[i<<1],hv[(i<<1)|1]);
}
void pd(int i) {
    work(i<<1,tag[i],ht[i]);
    work((i<<1)|1,tag[i],ht[i]);
    tag[i].clear(),ht[i].clear();
}
void build(int s,int t,int i) {
    tag[i].clear(),ht[i].clear();
    if(s==t) {v[i]=hv[i]=a[s];return;}
    int mid=(s+t)>>1;
    build(s,mid,i<<1),build(mid+1,t,(i<<1)|1);
    up(i);
}
int query(int o,int l,int r,int s,int t,int i) {
    if(l<=s&&t<=r) return o?hv[i]:v[i];
    int mid=(s+t)>>1,re=-inf; pd(i);
    if(l<=mid) re=query(o,l,r,s,mid,i<<1);
    if(mid+1<=r) re=max(re,query(o,l,r,mid+1,t,(i<<1)|1));
    return re;
}
void chan(int l,int r,int s,int t,int i,node k) {
    if(l<=s&&t<=r) {work(i,k,k);return;}
    int mid=(s+t)>>1; pd(i);
    if(l<=mid) chan(l,r,s,mid,i<<1,k);
    if(mid+1<=r) chan(l,r,mid+1,t,(i<<1)|1,k);
    up(i);
}
int main()
{
    char ch[10];int x,y,z;
    n=read();
    for(RI i=1;i<=n;++i) a[i]=read();
    build(1,n,1);
    m=read();
    while(m--) {
        scanf("%s",ch),x=read(),y=read();
        if(ch[0]=='Q') printf("%d\n",query(0,x,y,1,n,1));
        else if(ch[0]=='A') printf("%d\n",query(1,x,y,1,n,1));
        else if(ch[0]=='P') chan(x,y,1,n,1,(node){read(),-inf});
        else chan(x,y,1,n,1,(node){-inf,read()});
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值