因为线段树更偏向实现过程的解读吧,我就把解释写成注释了
#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;
}