(笔记)线段树维护区间历史最值 最值操作 lazytag 应用

区间历史最值 最值操作

P6242 【模板】线段树 3(区间最值操作、区间历史最值)

先解决区间最值操作的问题。

考虑通过划分数域将区间最值操作转化为区间加减。以 \(i\in [l,r],A_i\leftarrow max(A_i,v)\) 的区间操作为例。我们对于线段树每个节点记录一个最大值 \(mx\) 及其出现次数 \(cnt\) 和一个次大值 \(cmx\),暴力递归直到存在区间使得 \(cmx<v<mx\),这时候只需要把 \(cnt\)\(mx\) 全部变成 \(v\) 即可,这是 lazytag 容易维护的。可以证明当不存在其他修改操作(如区间加等)时,该操作的时间复杂度是 \(O(m\log n)\) 的。

如果存在修改操作,时间复杂度就是 \(O(m\log^2 n)\) 的,效率相当优秀了,但我不会证www

再来解决区间历史最值得问题。这里我们只需要做一个扩展,对于每个 lazytag,复制一个新的历史 lazytag,每次向下传递的时候通过用原信息和历史 lazytag 更新历史最值,原信息和原 lazytag 更新原值即可,需要注意的是要先更新历史值,以免被覆盖。

在本题中,我们根据操作特性设计两个原始 lazytag,表示对 \(mx\) 的区间加和对非 \(mx\) 的区间加,再扩展两个历史 lazytag

每次做标记相当于分别给 \(mx\) 和非 \(mx\) 都搞了一个区间加,写在一个函数中即可。

//tgs -> tagsum,tgsh -> tagsum history
//tgm -> tagmax,tgmh -> tagmax history
void mktag(int p,int v1,int v2,int v3,int v4){
	int l=Ln[p],r=Rn[p];
	sum[p]+=1ll*v1*cnt[p]+1ll*v2*(r-l+1-cnt[p]);
	mxb[p]=max(mxb[p],mxa[p]+v3);
	mxa[p]+=v1;
	if(cmx[p]!=-INF)cmx[p]+=v2;
	tgmh[p]=max(tgmh[p],tgm[p]+v3);
	tgsh[p]=max(tgsh[p],tgs[p]+v4);
	tgm[p]+=v1;tgs[p]+=v2;
}

我们发现历史最值更新上面所述的逻辑是相同的,需要注意的是本题还要更新 \(cmx\)

然后我们就基本解决了这题,注意一下 pushup 中的信息合并逻辑即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=5e5+5,INF=2e9;
int a[N],L,R,k,n,m;
struct Sgt{
	#define ls p<<1
	#define rs p<<1|1
	#define mid ((l+r)>>1)
	int tgm[N<<2],tgs[N<<2],tgmh[N<<2],tgsh[N<<2];
	int mxa[N<<2],mxb[N<<2],cnt[N<<2],cmx[N<<2];
	int Ln[N<<2],Rn[N<<2];
	LL sum[N<<2];
	void pushup(int p){
		sum[p]=sum[ls]+sum[rs];
		mxa[p]=max(mxa[ls],mxa[rs]);
		mxb[p]=max(mxb[ls],mxb[rs]);
		if(mxa[ls]==mxa[rs])cnt[p]=cnt[ls]+cnt[rs],cmx[p]=max(cmx[ls],cmx[rs]);
		else if(mxa[ls]<mxa[rs])cnt[p]=cnt[rs],cmx[p]=max(mxa[ls],cmx[rs]);
		else cnt[p]=cnt[ls],cmx[p]=max(mxa[rs],cmx[ls]);
	}
	void build(int p,int l,int r){
		tgm[p]=tgs[p]=tgmh[p]=tgsh[p]=0;
		Ln[p]=l,Rn[p]=r;
		if(l==r){
			sum[p]=a[l];
			mxa[p]=mxb[p]=a[l];
			cnt[p]=1,cmx[p]=-INF;
			return ;
		}
		build(ls,l,mid);
		build(rs,mid+1,r);
		pushup(p);
	}
	void mktag(int p,int v1,int v2,int v3,int v4){
		int l=Ln[p],r=Rn[p];
		sum[p]+=1ll*v1*cnt[p]+1ll*v2*(r-l+1-cnt[p]);
		mxb[p]=max(mxb[p],mxa[p]+v3);
		mxa[p]+=v1;
		if(cmx[p]!=-INF)cmx[p]+=v2;
		tgmh[p]=max(tgmh[p],tgm[p]+v3);
		tgsh[p]=max(tgsh[p],tgs[p]+v4);
		tgm[p]+=v1;tgs[p]+=v2;
	}
	void pushdown(int p){
		int mxn=max(mxa[ls],mxa[rs]);
		if(mxa[ls]==mxn)
			mktag(ls,tgm[p],tgs[p],tgmh[p],tgsh[p]);
		else mktag(ls,tgs[p],tgs[p],tgsh[p],tgsh[p]);
		if(mxa[rs]==mxn)
			mktag(rs,tgm[p],tgs[p],tgmh[p],tgsh[p]);
		else mktag(rs,tgs[p],tgs[p],tgsh[p],tgsh[p]);
		tgm[p]=tgs[p]=tgmh[p]=tgsh[p]=0;
	}
	void update_add(int p){
		int l=Ln[p],r=Rn[p];
		if(L<=l&&r<=R){
			mktag(p,k,k,k,k);
			return ;
		}
		pushdown(p);
		if(L<=mid)update_add(ls);
		if(R>mid)update_add(rs);
		pushup(p);
	}
	void update_min(int p){
		int l=Ln[p],r=Rn[p];
		if(k>=mxa[p])return ;
		if(L<=l&&r<=R&&cmx[p]<k){
			int v=mxa[p]-k;
			mktag(p,-v,0,-v,0);
			return ;
		}
		pushdown(p);
		if(L<=mid)update_min(ls);
		if(R>mid)update_min(rs);
		pushup(p);
	}
	LL quesum(int p){
		int l=Ln[p],r=Rn[p];
		if(L<=l&&r<=R)return sum[p];
		LL res=0;
		pushdown(p);
		if(L<=mid)res+=quesum(ls);
		if(R>mid)res+=quesum(rs);
		return res;
	}
	int quemxa(int p){
		int l=Ln[p],r=Rn[p];
		if(L<=l&&r<=R)return mxa[p];
		int res=-INF;
		pushdown(p);
		if(L<=mid)res=max(res,quemxa(ls));
		if(R>mid)res=max(res,quemxa(rs));
		return res;
	}
	int quemxb(int p){
		int l=Ln[p],r=Rn[p];
		if(L<=l&&r<=R)return mxb[p];
		int res=-INF;
		pushdown(p);
		if(L<=mid)res=max(res,quemxb(ls));
		if(R>mid)res=max(res,quemxb(rs));
		return res;
	}
	#undef ls
	#undef rs
	#undef mid
}T;
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	T.build(1,1,n);
	while(m--){
		int op;scanf("%d%d%d",&op,&L,&R);
		if(op==1){
			scanf("%d",&k);
			T.update_add(1);
		}
		else if(op==2){
			scanf("%d",&k);
			T.update_min(1);
		}
		else if(op==3)
			printf("%lld\n",T.quesum(1));
		else if(op==4)
			printf("%d\n",T.quemxa(1));
		else 
			printf("%d\n",T.quemxb(1));
	}
	return 0;
}

lazytag 应用

P4314 CPU 监控

搞清楚 lazytag 的运算逻辑很重要。本题有两个操作,一个是区间覆盖,一个是区间加,然后有求最大值和历史最大值的询问。针对两种操作,我们需要知道,lazytag 实际上记录的是在线段树节点上的操作序列,只是有些操作可以合并所以不需要每次都设一个 vector(也开不下)。那么每次操作我们就要弄清楚其中的逻辑。

对于一个节点的区间覆盖,第一次覆盖后的任何一次操作都可以视为区间覆盖,因为区间加只需要将覆盖值上下位移即可。那么实际上每个节点的操作序列只有:

\(\text{区间加 + 区间覆盖}\)

按照这个逻辑,我们 pushdown 时先更新区间加,再更新区间覆盖即可。在区间加时,如果存在之前的区间覆盖,那么直接放在覆盖标记上。

关于 #11 Hack:由于题目数据,我们需要给区间覆盖一个单独的 bool tag 以判断其是否为空 \(\texttt{tag}\)。判断是否要更新区间加 \(\texttt{tag}\) 时需要有 \(\texttt{tagsum}\)\(\texttt{tagsum hisotry}\) 任一 \(\texttt{tag}\)\(0\) 就更新,理由是经过若干次加操作,\(\texttt{tagsum}\) 最后可能为 \(0\),但是过程中可能大于 \(0\),所以 \(\texttt{tagsum history}\) 大于 \(0\)\(\texttt{tagsum history}\) 有可能为 0,但是可能是因为 \(\texttt{tagsum}\) 在更新过程中一直小于 \(0\)

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
const int INF=-(1ll<<31);
int L,R,k,a[N],n;
struct Sgt{
	#define ls p<<1
	#define rs p<<1|1
	#define mid ((l+r)>>1)
	int mx[N<<2],mxh[N<<2];
	int tg[N<<2],tgh[N<<2];
	bool psb[N<<2];
	int tgs[N<<2],tgsh[N<<2];
	int Ln[N<<2],Rn[N<<2];
	void pushup(int p){
		mx[p]=max(mx[ls],mx[rs]);
		mxh[p]=max(mxh[ls],mxh[rs]);
	}
	void build(int p,int l,int r){
		tgh[p]=tg[p]=INF;
		Ln[p]=l,Rn[p]=r;
		if(l==r){
			mx[p]=mxh[p]=a[l];
			return ;
		}
		build(ls,l,mid);
		build(rs,mid+1,r);
		pushup(p);
	}
	void mktag2(int p,int v1,int v2){
		mx[p]=v1;
		mxh[p]=max(mxh[p],v2);
		psb[p]=1;tg[p]=v1;
		tgh[p]=max(tgh[p],v2);
	}
	void mktag1(int p,int v1,int v2){
		if(psb[p])mktag2(p,mx[p]+v1,mx[p]+v2);
		else {
			mxh[p]=max(mxh[p],mx[p]+v2);
			mx[p]+=v1;
			tgsh[p]=max(tgsh[p],tgs[p]+v2);
			tgs[p]+=v1;
		}
	}
	void pushdown(int p){
		if(tgs[p]||tgsh[p]){
			mktag1(ls,tgs[p],tgsh[p]);
			mktag1(rs,tgs[p],tgsh[p]);
			tgs[p]=tgsh[p]=0;
		}
		if(psb[p]){
			mktag2(ls,tg[p],tgh[p]);
			mktag2(rs,tg[p],tgh[p]);
			psb[p]=0;
			tgh[p]=tg[p]=INF;
		}
	}
	void updadd(int p){
		int l=Ln[p],r=Rn[p];
		if(L<=l&&r<=R){
			mktag1(p,k,k);
			return ;
		}
		pushdown(p);
		if(L<=mid)updadd(ls);
		if(R>mid)updadd(rs);
		pushup(p);
	}
	void updfg(int p){
		int l=Ln[p],r=Rn[p];
		if(L<=l&&r<=R){
			mktag2(p,k,k);
			return ;
		}
		pushdown(p);
		if(L<=mid)updfg(ls);
		if(R>mid)updfg(rs);
		pushup(p);
	}
	int qmx(int p){
		int l=Ln[p],r=Rn[p];
		if(L<=l&&r<=R)return mx[p];
		pushdown(p);
		if(L<=mid&&R>mid)return max(qmx(ls),qmx(rs));
		if(L<=mid)return qmx(ls);
		return qmx(rs);
	}
	int qmxh(int p){
		int l=Ln[p],r=Rn[p];
		if(L<=l&&r<=R)return mxh[p];
		pushdown(p);
		if(L<=mid&&R>mid)return max(qmxh(ls),qmxh(rs));
		if(L<=mid)return qmxh(ls);
		return qmxh(rs);
	}
	#undef ls
	#undef rs
	#undef mid
}T;
char c;
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i];
	T.build(1,1,n);
	int E;cin>>E;
	while(E--){
		cin>>c>>L>>R;
		if(c=='Q')cout<<T.qmx(1)<<'\n';
		else if(c=='A')cout<<T.qmxh(1)<<'\n';
		else if(c=='P'){
			cin>>k;
			T.updadd(1);
		}
		else {
			cin>>k;
			T.updfg(1);
		}
	}
	return 0;
}
posted @ 2025-07-11 22:11  TBSF_0207  阅读(15)  评论(0)    收藏  举报