线段树的历史区间最值和区间抹平操作问题(P6242 线段树3)

板子

调试了大半天,终于切掉了,写写感悟与代码思路,加深理解。

 P6242 线段树3icon-default.png?t=N7T8https://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
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>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<>b>>c,cout<>b>>c,cout<

你可能感兴趣的:(算法)