洛谷P6242线段树3代码解释

57 阅读5分钟

 因为线段树更偏向实现过程的解读吧,我就把解释写成注释了

#include<iostream>
#include<cstdio>
using namespace std;

#define ll long long
#define INF 1e9

const int maxn=5*1e5+5; //数的总数,节点总数应为4倍的数的总数
ll maxa[maxn<<2],maxb[maxn<<2],se[maxn<<2],cnt[maxn<<2],sum[maxn<<2];
// 节点属性: maxa-区间最大值, maxb-区间历史最大值, se-区间严格次大值, cnt-区间最大值个数, sum-区间和
ll add1[maxn<<2],add2[maxn<<2],add3[maxn<<2],add4[maxn<<2];
// lazy-tag: add1-最大值变化量, add2-非最大值变化量, add3-最大值的历史最大值变化量, add4-非最大值的历史最大值变化量
//详细解读add3:最大值变化到最大值的历史最大值的增量(即:最大值的历史最大值-最大值)
//详细解读add4:非最大值变化到非最大值的历史最大值的增量(即:非最大值的历史最大值-非最大值)
ll a[maxn];
ll n,m;

inline ll ls(ll x) {return x<<1;}
inline ll rs(ll x) {return (x<<1)|1;}

//用子节点的节点属性更新当前节点的节点属性,即maxa,maxb,se,cnt,sum
void push_up(ll p){
    maxa[p]=max(maxa[ls(p)],maxa[rs(p)]);
    maxb[p]=max(maxb[ls(p)],maxb[rs(p)]);
    if(maxa[ls(p)]==maxa[rs(p)]){
        se[p]=max(se[ls(p)],se[rs(p)]);
        cnt[p]=cnt[ls(p)]+cnt[rs(p)];
    }else if(maxa[ls(p)]<maxa[rs(p)]){
        se[p]=max(maxa[ls(p)],se[rs(p)]);
        cnt[p]=cnt[rs(p)];
    }else{
        se[p]=max(maxa[rs(p)],se[ls(p)]);
        cnt[p]=cnt[ls(p)];
    }
    sum[p]=sum[ls(p)]+sum[rs(p)];
}

// 实际上是push_down操作的具体实施,包括:1.更新子节点的属性 2.更新子节点的lazy-tag
//k1:最大值的增量 k2:非最大值的增量 k3:最大值的历史最大值的增量 k4:非最大值的历史最大值的增量
void change(ll p,ll pl,ll pr,ll k1,ll k2,ll k3,ll k4){

    maxb[p]=max(maxb[p],maxa[p]+k3);
    maxa[p]+=k1;
    if(se[p]!=-INF) se[p]+=k2; // 如果次大值存在,则更新区间次大值
    sum[p]+=cnt[p]*k1+(pr-pl+1-cnt[p])*k2;
    add3[p]=max(add3[p],add1[p]+k3);
    add4[p]=max(add4[p],add2[p]+k4);
    add1[p]+=k1;
    add2[p]+=k2;
}

// 下传lazy-tags
void push_down(ll p,ll pl,ll pr){
    ll ma=max(maxa[ls(p)],maxa[rs(p)]); //!!!获取左右子节点中的最大值
    ll mid=(pl+pr)>>1;
    if(ma==maxa[ls(p)]){   // 最大值在左子节点中出现
        change(ls(p),pl,mid,add1[p],add2[p],add3[p],add4[p]); // 将所有标记(最大值和非最大值)下传到左子节点
    }
    else {
        change(ls(p),pl,mid,add2[p],add2[p],add4[p],add4[p]); // 将非最大值相关的标记当做最大值的标记下传到左子节点
    }
    if(ma==maxa[rs(p)]){   // 最大值在右子节点中出现
        change(rs(p),mid+1,pr,add1[p],add2[p],add3[p],add4[p]);
    }
    else {
        change(rs(p),mid+1,pr,add2[p],add2[p],add4[p],add4[p]);
    }
    add1[p]=add2[p]=add3[p]=add4[p]=0; // 清空当前节点的延迟标记,避免重复更新
}

// update_change操作:区间取min操作
void update_change(ll L,ll R,ll p,ll pl,ll pr,ll v){
    if(v>=maxa[p]) return; // 如果v大于等于当前节点最大值,则无需修改
    if(pl>=L && pr<=R && v>se[p]){ // v严格大于当前区间严格次大值(若v=se[p]不应该进行修改,否则次大值可能会变为不存在)
        //更新需要更新的节点属性和lazy-tag
        sum[p]-=(maxa[p]-v)*cnt[p];
        add1[p]-=(maxa[p]-v);
        maxa[p]=v;
        return;
    }
    push_down(p,pl,pr);
    ll mid=(pl+pr)>>1;
    if(L<=mid) update_change(L,R,ls(p),pl,mid,v);
    if(R>mid) update_change(L,R,rs(p),mid+1,pr,v);
    push_up(p);
}

// update_add操作:区间加法操作
void update_add(ll L,ll R,ll p,ll pl,ll pr,ll k){
    if(pl>=L && pr<=R){ // 如果当前区间被包含在修改区间内
        maxa[p]+=k;
        maxb[p]=max(maxb[p],maxa[p]);
        if(se[p]!=-INF) se[p]+=k;
        sum[p]+=(pr-pl+1)*k;
        add1[p]+=k;add2[p]+=k;
        add3[p]=max(add3[p],add1[p]);
        add4[p]=max(add4[p],add2[p]);
        return;
    }
    push_down(p,pl,pr);
    ll mid=(pl+pr)>>1;
    if(L<=mid) update_add(L,R,ls(p),pl,mid,k);
    if(R>mid) update_add(L,R,rs(p),mid+1,pr,k);
    push_up(p);
}

// query_sum操作:区间求和查询
ll query_sum(ll L,ll R,ll p,ll pl,ll pr){
    if(pl>=L && pr<=R) {
        return sum[p];
    }
    ll res=0;
    push_down(p,pl,pr); //!!!注意:查询时需要下传lazy-tag
    ll mid=(pl+pr)>>1;
    if(L<=mid) res+=query_sum(L,R,ls(p),pl,mid);
    if(R>mid) res+=query_sum(L,R,rs(p),mid+1,pr);
    return res;
    //因为只是查询,没有修改,所以不需要push_up
}

// query_maxa操作:区间最大值查询
ll query_maxa(ll L,ll R,ll p,ll pl,ll pr){
    if(pl>=L && pr<=R){
        return maxa[p];
    }
    ll res=-INF;
    push_down(p,pl,pr);
    ll mid=(pl+pr)>>1;
    if(L<=mid) res=max(res,query_maxa(L,R,ls(p),pl,mid));
    if(R>mid) res=max(res,query_maxa(L,R,rs(p),mid+1,pr));
    return res;
}

// query_maxb操作:区间历史最大值查询
ll query_maxb(ll L,ll R,ll p,ll pl,ll pr){
    if(pl>=L && pr<=R){
        return maxb[p];
    }
    ll res=-INF;
    push_down(p,pl,pr);
    ll mid=(pl+pr)>>1;
    if(L<=mid) res=max(res,query_maxb(L,R,ls(p),pl,mid));
    if(R>mid) res=max(res,query_maxb(L,R,rs(p),mid+1,pr));
    return res;
}

// build_tree操作:构建线段树
void build_tree(ll p,ll pl,ll pr){
    if(pl==pr){ // 如果是叶子节点
        maxa[p]=maxb[p]=sum[p]=a[pl]; // 初始化最大值、历史最大值、和为数组的原始值
        se[p]=-INF; // !!!初始化次大值为负无穷
        cnt[p]=1; // 初始化最大值个数为1
        return;
    }
    ll mid=(pl+pr)>>1;
    build_tree(ls(p),pl,mid);
    build_tree(rs(p),mid+1,pr);
    push_up(p);
}


int main()
{
    ios::sync_with_stdio(false);cin.tie(nullptr);

    cin>>n>>m;
    for(ll i=1;i<=n;i++){   // 读取原始数组
        cin>>a[i];
    }
    build_tree(1,1,n); // 初始化线段树
    for(ll i=1;i<=m;i++){
        ll op,l,r,k,v;
        cin>>op>>l>>r;
        switch(op){
        case 1:
            cin>>k;
            update_add(l,r,1,1,n,k);
            break;
        case 2:
            cin>>v;
            update_change(l,r,1,1,n,v);
            break;
        case 3:
            cout<<query_sum(l,r,1,1,n)<<"\n";
            break;
        case 4:
            cout<<query_maxa(l,r,1,1,n)<<"\n";
            break;
        case 5:
            cout<<query_maxb(l,r,1,1,n)<<"\n";
            break;
        }
    }
    return 0;
}