板子
调试了大半天,终于切掉了,写写感悟与代码思路,加深理解。
P6242 线段树3https://www.luogu.com.cn/problem/P6242
大致题意:维护一个数组, 支持一下五种操作:
- 区间加减
- 区间抹平(使所有大于
v
的数变成v
) 即进行min操作 - 区间求和
- 区间最值
- 区间历史最值
整道题的重难点在于两个:
1.如何将区间抹平,并将其与区间最值联系起来(高效率操作)
2.如何求取历史区间最值
解决方法在于各种懒标记的运用(吉如一老师的论文,有兴趣可以去看看)
记得开long long!记得开long long!记得开long long!(为了这个long long调了一个多钟)
鉴于是个人整理文,直接在代码中添加注释,有疑惑之处欢迎交流
// Problem:
// P6242 【模板】线段树 3(区间最值操作、区间历史最值)
//
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P6242
// Memory Limit: 500 MB
// Time Limit: 3500 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<iostream>
using namespace std;
#define endl '\n'
#define lc p<<1
#define rc p<<1|1
using ll=long long;
const int N=5e5+10;
#define int long long
//不开long long见祖宗,这里我本来为了省空间,只是给求和加了ll,但是一直过不了
//结果抱着一试的心态,#define int long long秒了,欲哭无泪~
int n,m;
struct tree{//用数组也行,似乎更节省空间,但是有个佬说用结构体空间是连续的速度更快
int l,r,se,maxa,maxb,cnt;//这一行开的是为了维护min操作和最大值
//se 次大 cnt 最大的个数
int add1,add2,add3,add4;
//1是最大,2是非最大,原因在于min操作只对最大产生影响
//(3与4)为了维护历史最大值,保证在下传的时候记录一个历史最大懒标记,这样实时的就不会影响了
ll sum;//区间和
}tr[N*4];
inline void pushup(int p){ //上传,这里比较简单
tr[p].sum=(tr[lc].sum+tr[rc].sum);
tr[p].maxa=max(tr[lc].maxa,tr[rc].maxa);
tr[p].maxb=max(tr[lc].maxb,tr[rc].maxb);//求三个结果都是正常的比较
if(tr[rc].maxa==tr[lc].maxa){//如果相等,两边的最大都能上传
tr[p].cnt=tr[lc].cnt+tr[rc].cnt;
tr[p].se=max(tr[lc].se,tr[rc].se);
}
else{
tr[p].se=max(max(tr[lc].se,tr[rc].se),min(tr[rc].maxa,tr[lc].maxa));
//精简,就是找一个第二大的,所以把次大的最大值和最大的最小值比较大小
tr[p].cnt=(tr[lc].maxa>tr[rc].maxa) ? tr[lc].cnt : tr[rc].cnt;
//三目运算符,哪个大就用哪个的次数
}
}
inline void change(int a,int b,int c,int d,int p){//修改操作,这里和pushdown比较精髓
tr[p].sum+= a*tr[p].cnt*1ll+b*(tr[p].r-tr[p].l+1-tr[p].cnt)*1ll;
//对区间和操作,要分清最大和非最大(懒标记)
tr[p].maxb=max(tr[p].maxb,tr[p].maxa+c);
tr[p].maxa+=a;
//要先操作maxb,因为maxb是历史区间,是下传前的最大(历史),所以要先操作,再下传懒标记
//如果先下传了,就相当于直接改变了(实时),因为历史的堆积再那里没有下传
//我这里能求得历史最大是历史的,不能被实时的(maxa)所影响
if(tr[p].se!=-2e9) tr[p].se+=b;
tr[p].add3=max(tr[p].add3,tr[p].add1+c);
tr[p].add4=max(tr[p].add4,tr[p].add2+d);
tr[p].add1+=a;
tr[p].add2+=b;
//这里与上面是同样的想法
}
inline void pushdown(int p){
int maxn=max(tr[lc].maxa,tr[rc].maxa);//这里要先拉出来,因为change会对maxa有影响
if(tr[lc].maxa==maxn) change(tr[p].add1,tr[p].add2,tr[p].add3,tr[p].add4,lc);
else change(tr[p].add2,tr[p].add2,tr[p].add4,tr[p].add4,lc);
//如果某个是最大,就用最大修,如果没有,就用非最大懒标记修
if(tr[rc].maxa==maxn) change(tr[p].add1,tr[p].add2,tr[p].add3,tr[p].add4,rc);
else change(tr[p].add2,tr[p].add2,tr[p].add4,tr[p].add4,rc);
tr[p].add1=0;tr[p].add2=0;tr[p].add3=0;tr[p].add4=0;//用完要清零
}
inline void add_update(int p,int l,int r,int k){ //加减更新
if(tr[p].l>=l&&tr[p].r<=r){
tr[p].sum+=1ll*k*(tr[p].r-tr[p].l+1);//维护区间和
tr[p].maxa+=k;//最大值一定会变大
tr[p].maxb=max(tr[p].maxb,tr[p].maxa);//最大值变了,看看历史区间最大变不变
if(tr[p].se!=-2e9) tr[p].se+=k;//如果有次大,次大也要维护
tr[p].add1+=k;tr[p].add2+=k;//懒标记要变
tr[p].add3=max(tr[p].add3,tr[p].add1);
tr[p].add4=max(tr[p].add4,tr[p].add2);//看看历史区间要不要变
return;
}
pushdown(p);
int m=(tr[p].l+tr[p].r)>>1;
if(l<=m) add_update(lc,l,r,k);
if(r>m) add_update(rc,l,r,k);
pushup(p);
}
inline void update_min(int p,int l,int r,int k){//维护min更新
if(k>=tr[p].maxa) return;//这里不能少,如果大于就不会更新
if(tr[p].l>=l&&tr[p].r<=r&&tr[p].se<k){//小于最大,大于次大,且在区间内,直接更新掉maxa
int c=tr[p].maxa-k;//求差
tr[p].sum -= c*tr[p].cnt*1ll;//区间要减掉这个差
tr[p].maxa=k;tr[p].add1-=c;//maxa要变,懒标记也要减少这个最大值
//之前曾经怀疑add1为什么要变,后来发现它是加减,所以说,add1是有负数的,记得看题
return;
}
pushdown(p);
int m=(tr[p].l+tr[p].r)>>1;
if(l<=m) update_min(lc,l,r,k);
if(r>m) update_min(rc,l,r,k);
pushup(p);
}
inline int query_maxb(int p,int l,int r){
if(tr[p].l>=l&&tr[p].r<=r) return tr[p].maxb;
pushdown(p);
int m=(tr[p].l+tr[p].r)>>1;
int ans=-2e9;
if(l<=m) ans=query_maxb(lc,l,r);
//第一次不用max,因为-2e9包底了(我也不知道为为什么,用INT_MIN也行啦)
if(r>m) ans=max(ans,query_maxb(rc,l,r));
return ans;
}
inline int query_maxa(int p,int l,int r){
if(tr[p].l>=l&&tr[p].r<=r) return tr[p].maxa;
pushdown(p);
int m=(tr[p].l+tr[p].r)>>1;
int ans=-2e9;
if(l<=m) ans=query_maxa(lc,l,r);
if(r>m) ans=max(ans,query_maxa(rc,l,r));
return ans;
}
inline ll query_sum(int p,int l,int r){//查询,这里我还特意开了long long(结果卡住了)
if(tr[p].l>=l&&tr[p].r<=r) return tr[p].sum;
pushdown(p);
int m=(tr[p].l+tr[p].r)>>1;
ll ans=0;
if(l<=m) ans+=query_sum(lc,l,r);
if(r>m) ans+=query_sum(rc,l,r);
return ans;
}
void build(int p,int l,int r){
tr[p]={l,r};
if(l==r){
cin>>tr[p].sum;tr[p].maxa=tr[p].maxb=tr[p].sum;
tr[p].se=-2e9;tr[p].cnt=1;
return;//注意要return掉
}
int m=(l+r)>>1;
build(lc,l,m);build(rc,m+1,r);
pushup(p);
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m;
build(1,1,n);//在建树的时候读入数据,可以一定的节省时间和空间
int a,b,c,d;
while(m--){
cin>>a;
if(a==1) cin>>b>>c>>d,add_update(1,b,c,d);
if(a==2) cin>>b>>c>>d,update_min(1,b,c,d);
if(a==3) cin>>b>>c,cout<<query_sum(1,b,c)<<endl;
if(a==4) cin>>b>>c,cout<<query_maxa(1,b,c)<<endl;
if(a==5) cin>>b>>c,cout<<query_maxb(1,b,c)<<endl;//不解释
}
return 0;
}