(笔记)线段树最大子段和

题目链接:P2572 [SCOI2010] 序列操作

前言:↑↓玩意害得我调了整整两个小时(Angry)

策略

结合最大字段和维护思想与区间赋值思想,加上懒标记赋值时的优先级次序,即可维护。

维护最大字段和

(很明显我并不是很熟所以拿出来单独讲)

接下来是最大字段和时间:

例题:【AcWing】245. 你能回答这些问题吗P4513 小白逛公园

子段合并本身与查询过程相同,但是

类似思想,但是本题只是利用了思想精髓,并不是最大子段和原封不动搬过来。子段和的合并可以分三种情况:

  1. 直接来自左儿子区间内 \(ans_p=ans_{ls}\)

  2. 直接来自右儿子区间内 \(ans_p=ans_{rs}\)

  3. 左端点在左儿子区间内,右端点在右儿子区间内,此时我们需要每个点多维护一个\(pre\)(prefix)和\(suf\)(suffix)分别代表当前区间以左端点为起点的最大连续字段和和以右端点为终点的最大连续字段和,此时即可得答案 \(ans_p=suf_{ls}+pre_{rs}\)

以上三种情况取最大值即可

维护 \(pre\)\(suf\) 也不难,以 \(pre\) 为例,在 \(pre_{ls}\)\(sum_{ls}+pre_{rs}\) 中取最大值即可。

以下是查询代码(修改同理):

node que(int p,int l,int r,int L,int R){
		if(L==l&&r==R){//当前区间刚好等于查询区间
			return (node){ans[p],sum[p],lmx[p],rmx[p]};
		}
		int res=-INF;
		if(R<=mid)return que(ls,l,mid,L,R);//在左边查找
		if(L>mid)return que(rs,mid+1,r,L,R);//在右边查找
		node x=que(ls,l,mid,L,mid);//L~mid的区间
		node y=que(rs,mid+1,r,mid+1,R);//mid+1~R的区间
		node Ans;
		Ans.sum=x.sum+y.sum;
		Ans.lmx=max(x.lmx,x.sum+y.lmx);//维护pre和suf
		Ans.rmx=max(y.rmx,y.sum+x.rmx);//这里lmx为pre,rmx为suf
		Ans.ans=max(x.ans,max(y.ans,x.rmx+y.lmx));
  //分别是情况1,2,3
		return Ans;
	}

区间赋值

需要注意,在区间赋值打 lazytag 时优先级总是最高的,这意味着区间赋值操作可以覆盖其它所有操作,在代码中 pushdown 也要注意这一点。

实现

主要考虑懒标记优先级的问题。对于题目中操作 \(0,1,\)pushdown 后将其它标记置 \(0\) 即可。对于题目中的操作 \(2\),需要考虑当有来自操作 \(0,1\) 的 lazytag 时,直接翻转,让原来的操作 \(0\) 变成 \(1\)\(1\) 变成 \(0\)。否则,执行翻转操作。

考虑到与最大子段和思想结合,需要在维护连续 \(1\) 的个数的 \(pre\)\(suf\) 时,同时还要维护连续 \(0\) 的个数的 \(npre\)\(nsuf\),这样区间翻转直接交换两者即可。

代码如下:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5,INF=1e9;
int n,m;
bool a[N];
struct Tre{
	struct node{
		int ans,sum,pre,suf;
	};
	#define ls (p<<1)
	#define rs (p<<1|1)
	#define mid ((l+r)>>1)
	bool tag1[N<<2],tag2[N<<2];
	bool tag3[N<<2];
	int sum[N<<2],ans[N<<2],pre[N<<2],suf[N<<2];
	int nans[N<<2],npre[N<<2],nsuf[N<<2];
	void pushup(int p,int l,int r){
		sum[p]=sum[ls]+sum[rs];
		ans[p]=max(ans[ls],ans[rs]);
		int L=mid-l+1,R=r-mid;
		
		pre[p]=max(pre[ls],(sum[ls]==L ? L+pre[rs]:0ll));
		suf[p]=max(suf[rs],(sum[rs]==R ? R+suf[ls]:0ll));
		ans[p]=max(ans[p],suf[ls]+pre[rs]);
		
		nans[p]=max(nans[ls],nans[rs]);
		
		npre[p]=max(npre[ls],(sum[ls]==0 ? L+npre[rs]:0ll));
		nsuf[p]=max(nsuf[rs],(sum[rs]==0 ? R+nsuf[ls]:0ll));
		nans[p]=max(nans[p],nsuf[ls]+npre[rs]);
	}
	void pushdown(int p,int l,int r){
		int L=mid-l+1,R=r-mid;
		if(tag1[p]){
			sum[ls]=sum[rs]=ans[ls]=ans[rs]=0;
			pre[ls]=pre[rs]=suf[ls]=suf[rs]=0;
			
			nans[ls]=npre[ls]=nsuf[ls]=L;
			nans[rs]=npre[rs]=nsuf[rs]=R;
			
			tag1[ls]=1,tag2[ls]=0,tag3[ls]=0;
			tag1[rs]=1,tag2[rs]=0,tag3[rs]=0;
			tag1[p]=0;
		}
		else if(tag2[p]){
			nans[ls]=nans[rs]=0;
			npre[ls]=npre[rs]=nsuf[ls]=nsuf[rs]=0;
			
			sum[ls]=ans[ls]=L;
			pre[ls]=suf[ls]=L;
			sum[rs]=ans[rs]=R;
			pre[rs]=suf[rs]=R;
		
			
			tag1[ls]=0,tag2[ls]=1,tag3[ls]=0;
			tag1[rs]=0,tag2[rs]=1,tag3[rs]=0;
			tag2[p]=0;
		}
		else if(tag3[p]){
			sum[ls]=L-sum[ls];
			sum[rs]=R-sum[rs];
			swap(nans[ls],ans[ls]);swap(nans[rs],ans[rs]);
			swap(nsuf[ls],suf[ls]);swap(nsuf[rs],suf[rs]);
			swap(npre[ls],pre[ls]);swap(npre[rs],pre[rs]);
			
			if(tag1[ls])tag1[ls]=0,tag2[ls]=1,tag3[ls]=0;
			else if(tag2[ls])tag2[ls]=0,tag1[ls]=1,tag3[ls]=0;
			else tag3[ls]^=1;
				
			if(tag1[rs])tag1[rs]=0,tag2[rs]=1,tag3[rs]=0;
			else if(tag2[rs])tag2[rs]=0,tag1[rs]=1,tag3[rs]=0;
			else tag3[rs]^=1;
			tag3[p]=0;
		}
	}
	void build(int p,int l,int r){
		if(l==r){
			sum[p]=ans[p]=a[l];
			pre[p]=suf[p]=a[l];
			nans[p]=!a[l];
			npre[p]=nsuf[p]=!a[l];
			return ;
		}
		build(ls,l,mid);
		build(rs,mid+1,r);
		pushup(p,l,r);
	}
	void upd(int p,int l,int r,int L,int R,int opt){
		pushdown(p,l,r);
		if(L<=l&&r<=R){
			if(opt==1){
				sum[p]=ans[p]=0;
				pre[p]=suf[p]=0;
				nans[p]=npre[p]=nsuf[p]=r-l+1;
				tag1[p]=1,tag2[p]=0,tag3[p]=0;
			}
			else if(opt==2){
				nans[p]=0;
				npre[p]=nsuf[p]=0;
			
				sum[p]=ans[p]=r-l+1;
				pre[p]=suf[p]=r-l+1;
				
				tag1[p]=0,tag2[p]=1,tag3[p]=0;
			}
			else if(opt==3){
				sum[p]=r-l+1-sum[p];
				swap(nans[p],ans[p]);
				swap(nsuf[p],suf[p]);
				swap(npre[p],pre[p]);
				
				if(tag1[p])tag1[p]=0,tag2[p]=1;
				else if(tag2[p])tag2[p]=0,tag1[p]=1;
				else tag3[p]=1;
			}
			return ;
		}
		if(L<=mid)upd(ls,l,mid,L,R,opt);
		if(R>mid)upd(rs,mid+1,r,L,R,opt);
		pushup(p,l,r);
	}
	int query(int p,int l,int r,int L,int R){
		pushdown(p,l,r);
		if(L<=l&&r<=R){
			return sum[p];
		}
		int res=0;
		if(L<=mid)res+=query(ls,l,mid,L,R);
		if(R>mid)res+=query(rs,mid+1,r,L,R);
		pushup(p,l,r);
		return res;
	}
	node que(int p,int l,int r,int L,int R){
		pushdown(p,l,r);
		if(L==l&&r==R){
			return (node){ans[p],sum[p],pre[p],suf[p]};
		}
		if(R<=mid)return que(ls,l,mid,L,R);
		if(L>mid)return que(rs,mid+1,r,L,R);
		
		node x=que(ls,l,mid,L,mid);
		node y=que(rs,mid+1,r,mid+1,R);
		
		int lsiz=mid-L+1,rsiz=R-mid;
		node Ans;
		Ans.sum=x.sum+y.sum;
		Ans.pre=max(x.pre,x.sum==lsiz?x.sum+y.pre:0ll);
		Ans.suf=max(y.suf,y.sum==rsiz?y.sum+x.suf:0ll);
		Ans.ans=max(x.ans,max(y.ans,x.suf+y.pre));
		pushup(p,l,r);
		return Ans;
	}
}T;

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>a[i];
	T.build(1,1,n);
	for(int i=1;i<=m;i++){
		int opt,x,y;
		cin>>opt>>x>>y;
		x++;y++;
		if(opt<3){
			T.upd(1,1,n,x,y,opt+1);
		}
		else if(opt==3){
			cout<<T.query(1,1,n,x,y)<<'\n';
		}
		else if(opt==4){
			cout<<T.que(1,1,n,x,y).ans<<'\n';
		}
	}
	return 0;
}

其他好题

GSS5 - Can you answer these queries V

分讨题,思路不详细分析,主要分为区间是否有重合两种情况考虑,然后只需要合理维护即可。

值得注意的是,由于最大子段和并不包括空子段,计算的时候要注意这一性质并格外小心,否则不慎很可能得到错误的答案。

代码贴贴:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e5+5,M=1e5+5,INF=1e9;
LL n,m,a[N];
struct Tre{
	struct node{
		LL ans,sum,lmx,rmx;
	};
	#define ls p<<1
	#define rs p<<1|1
	#define mid ((l+r)>>1)
	LL sum[N<<2],ans[N<<2],lmx[N<<2],rmx[N<<2];
	void pushup(int p){
		sum[p]=sum[ls]+sum[rs];
		lmx[p]=max(lmx[ls],sum[ls]+lmx[rs]);
		rmx[p]=max(rmx[rs],sum[rs]+rmx[ls]);
		ans[p]=max(ans[ls],max(ans[rs],rmx[ls]+lmx[rs]));
	}
	node que(int p,int l,int r,int L,int R){
		if(L>R)return (node){0,0,0,0};
		if(L<=l&&r<=R){
			return (node){ans[p],sum[p],lmx[p],rmx[p]};
		}
		if(R<=mid)return que(ls,l,mid,L,R);
		if(L>mid)return que(rs,mid+1,r,L,R);
		node x=que(ls,l,mid,L,mid);
		node y=que(rs,mid+1,r,mid+1,R);
		node Ans;
		Ans.sum=x.sum+y.sum;
		Ans.lmx=max(x.lmx,x.sum+y.lmx);
		Ans.rmx=max(y.rmx,y.sum+x.rmx);
		Ans.ans=max(x.ans,max(y.ans,x.rmx+y.lmx));
		return Ans;
	}
	void build(int p,int l,int r){
		if(l==r){
			ans[p]=sum[p]=a[l];
			lmx[p]=rmx[p]=a[l];
			return ;
		}
		build(ls,l,mid);
		build(rs,mid+1,r);
		pushup(p);
	}
}T;
LL que(int ax,int ay,int bx,int by){
	if(ay<bx){
		return T.que(1,1,n,ay+1,bx-1).sum+T.que(1,1,n,ax,ay).rmx+T.que(1,1,n,bx,by).lmx;
	}
	else {
		LL ch1=T.que(1,1,n,ax,bx).rmx+T.que(1,1,n,bx,by).lmx-a[bx];
		LL ch2=T.que(1,1,n,ax,ay).rmx+T.que(1,1,n,ay,by).lmx-a[ay];
		LL ch3=T.que(1,1,n,bx,ay).ans;
		return max(ch3,max(ch1,ch2));
	}
}
int G;
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>G;
	while(G--){
		cin>>n;
		for(int i=1;i<=n;i++)cin>>a[i];
		T.build(1,1,n);
		cin>>m;
		for(int i=1;i<=m;i++){
			LL ax,ay,bx,by;
			cin>>ax>>ay>>bx>>by;
			cout<<que(ax,ay,bx,by)<<'\n';
		}
	}
	
	return 0;
}

GSS2 - Can you answer these queries II

题解区的一句话,很有借鉴意义:

做题经验告诉我们,要处理有关区间去重的问题时,我们常用到离线算法,本题亦是如此。

posted @ 2025-04-24 14:53  TBSF_0207  阅读(24)  评论(0)    收藏  举报