吉司机线段树

吉司机线段树

为了方便说板子,这里直接把板子题放上去讲了。

线段树 3

简单说一下 5 个操作都在干什么:

  • 区间加一个数。

  • 区间和一个数取最小值。

  • 区间求和。

  • 区间求最大值。

  • 区间求历史最大值。

好了,前 4 个操作如果单独拉出来出成一道题,显然是好做的,于是我们的难点就是最后一个操作。

因此,我们的线段树要维护很多东西:区间左右端点,区间和,最大值,严格次大值等,具体注释放在下面代码里了,因为实在太多了:

struct node{
	int l,r,sum;
	int mx_st,mx_se,mx_hi,mx_num;
	int lzy_st,lzy_hi,lzy_se,lzy_he;
	/*
	mx_st 区间最大,mx_se 区间严格次大,mx_hi 区间历史最大,mx_sum 区间最大出现次数
	lzy_st 最大值加减标记,lzy_hi 历史最大值加减标记
	lzy_se 非最大值加减标记,lzy_he 非历史最大值加减标记
	*/
}tr[N<<2];

这里解释一下区间最大出现次数指的是在一个区间 [l,r] 中有几个数是当前区间的最大值。

接下来我们分模块讲一下各个操作:

建树

其他的部分和正常线段树一样,不过因为叶子节点只有一个值,故严格次大值应该附成极小值。

void build(int u,int l,int r){
	tr[u]={l,r};
	if(l==r){
		tr[u].sum=tr[u].mx_st=tr[u].mx_hi=a[l];
		tr[u].mx_num=1;
		tr[u].mx_se=-inf;
		return;
	}
	int mid=l+r>>1;
	build(u<<1,l,mid);
	build(u<<1|1,mid+1,r);
	pushup(u);
}

上传

我们需要更新的是区间和,最大值,严格次大值,历史最大值,最大值出现次数。

这里说一下不太好弄的严格次大值:显然如果两个儿子的最大值相同,那么严格次大值就是两边的严格次大值的较大的那个。

否则,我们设儿子 a 的最大值比儿子 b 的最大值更大,于是严格次大值就是儿子 a 的严格次大值和儿子 b 的最大值的较大值。

其它的更新见代码:

void pushup(int u){
	tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
	tr[u].mx_st=max(tr[u<<1].mx_st,tr[u<<1|1].mx_st);
	tr[u].mx_hi=max(tr[u<<1].mx_hi,tr[u<<1|1].mx_hi);
	if(tr[u<<1].mx_st==tr[u<<1|1].mx_st){
		tr[u].mx_num=tr[u<<1].mx_num+tr[u<<1|1].mx_num;
		tr[u].mx_se=max(tr[u<<1].mx_se,tr[u<<1|1].mx_se);
	}
	else if(tr[u<<1].mx_st>tr[u<<1|1].mx_st){
		tr[u].mx_num=tr[u<<1].mx_num;
		tr[u].mx_se=max(tr[u<<1].mx_se,tr[u<<1|1].mx_st);
	}
	else{
		tr[u].mx_num=tr[u<<1|1].mx_num;
		tr[u].mx_se=max(tr[u<<1].mx_st,tr[u<<1|1].mx_se);
	}
}

下传

这里拆成了两个部分 modifypushdown

先说一下 modify

我们传进去的参数有区间最大值,区间历史最大值,区间非最大值和区间非历史最大值这四个玩意要加上的数,同时要更新一堆东西。

  • sum:区间最大值的个数乘上最大值加上的数,其他的数乘上非最大值加上的数。

  • mxhi:首先还没有更新的 mxst 已经是历史了,所以我们要在原来的历史最大值和 mxst 加上历史最大值加上的数中取最大值。

  • mxst:比较显然,直接加上最大值要加的数即可。

  • mxse:如果这个区间有严格次大值,就加上非最大值加上的数。

  • lzyhi:这时还没有更新的 lzyst 也已经成为了历史,所以按照 mxhi 的方式进行更新就行了。

  • lzyst:直接加上最大值应该加的数即可。

  • lzyhe:此时的 lzyse 也已经成为了历史,所以按照 lzyhi 的方式进行更新即可。

  • lzyse:直接加上非最大值应该加上的数即可。

然后放一下代码:

void modify(int u,int val_st,int val_hi,int val_se,int val_he){
	tr[u].sum+=(tr[u].mx_num*val_st+(tr[u].r-tr[u].l+1-tr[u].mx_num)*val_se);
	tr[u].mx_hi=max(tr[u].mx_hi,tr[u].mx_st+val_hi);
	tr[u].mx_st+=val_st;
	if(tr[u].mx_se!=-inf)tr[u].mx_se+=val_se;
	tr[u].lzy_hi=max(tr[u].lzy_hi,tr[u].lzy_st+val_hi);
	tr[u].lzy_st+=val_st;
	tr[u].lzy_he=max(tr[u].lzy_he,tr[u].lzy_se+val_he);
	tr[u].lzy_se+=val_se;
}

好了,然后我们继续说 pushdown

首先我们记 mx 为当前区间的最大值,下面我们进行分情况讨论:

如果一个区间的最大值为 mx,则我们直接按照把四个懒标记传进 modify 里即可。

否则,都不是最大值,那么我们把应该传进最大值和历史最大值懒标记的部分传进非最大值和历史非最大值的懒标记即可。

下面放一下代码:

void pushdown(int u){
	int maxx=max(tr[u<<1].mx_st,tr[u<<1|1].mx_st);
	if(tr[u<<1].mx_st==maxx)modify(u<<1,tr[u].lzy_st,tr[u].lzy_hi,tr[u].lzy_se,tr[u].lzy_he);
	else modify(u<<1,tr[u].lzy_se,tr[u].lzy_he,tr[u].lzy_se,tr[u].lzy_he);
	if(tr[u<<1|1].mx_st==maxx)modify(u<<1|1,tr[u].lzy_st,tr[u].lzy_hi,tr[u].lzy_se,tr[u].lzy_he);
	else modify(u<<1|1,tr[u].lzy_se,tr[u].lzy_he,tr[u].lzy_se,tr[u].lzy_he);
	tr[u].lzy_st=tr[u].lzy_hi=tr[u].lzy_se=tr[u].lzy_he=0;
}

区间加

非常好做,按照正常线段树的方式直接在区间被完全包含时进行 modify 即可,所以直接看一下代码就行了:

void modify_add(int u,int L,int R,int v){
	if(tr[u].l>=L&&tr[u].r<=R){
		modify(u,v,v,v,v);
		return;
	}
	int mid=tr[u].l+tr[u].r>>1;
	pushdown(u);
	if(L<=mid)modify_add(u<<1,L,R,v);
	if(R>mid)modify_add(u<<1|1,L,R,v);
	pushup(u);
}

区间取最小值

最后一个比较恶心的东西,考虑一个事情,当前区间最大值已经不比要修改的值大了直接返回就好,但是这样在修改的值极小的时候复杂度会直接炸掉,所以这时就是我们用上之前维护的严格次大值派上用场的时候了。

我们只需要找到 mxst<v<mxse 的区间修改区间的最大值和历史最大值即可,并且可以把取最小值的操作改成加法操作,所以直接使用 modify 改就好了。

然后放一下代码:

void modify_min(int u,int L,int R,int v){
	if(tr[u].mx_st<=v)return;
	if(tr[u].l>=L&&tr[u].r<=R&&tr[u].mx_se<v){
		modify(u,v-tr[u].mx_st,v-tr[u].mx_st,0,0);
		return;
	}
	int mid=tr[u].l+tr[u].r>>1;
	pushdown(u);
	if(L<=mid)modify_min(u<<1,L,R,v);
	if(R>mid)modify_min(u<<1|1,L,R,v);
	pushup(u);
}

一堆查询

没啥好说的,按照正常线段树的查法查就行了,代码见最后的代码里。

代码(合集版)

#include<bits/stdc++.h>
#define int long long
#define N 500005
#define inf 2e18
using namespace std;
int n,m,a[N];
struct node{
	int l,r,sum;
	int mx_st,mx_se,mx_hi,mx_num;
	int lzy_st,lzy_hi,lzy_se,lzy_he;
}tr[N<<2];
void pushup(int u){
	tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
	tr[u].mx_st=max(tr[u<<1].mx_st,tr[u<<1|1].mx_st);
	tr[u].mx_hi=max(tr[u<<1].mx_hi,tr[u<<1|1].mx_hi);
	if(tr[u<<1].mx_st==tr[u<<1|1].mx_st){
		tr[u].mx_num=tr[u<<1].mx_num+tr[u<<1|1].mx_num;
		tr[u].mx_se=max(tr[u<<1].mx_se,tr[u<<1|1].mx_se);
	}
	else if(tr[u<<1].mx_st>tr[u<<1|1].mx_st){
		tr[u].mx_num=tr[u<<1].mx_num;
		tr[u].mx_se=max(tr[u<<1].mx_se,tr[u<<1|1].mx_st);
	}
	else{
		tr[u].mx_num=tr[u<<1|1].mx_num;
		tr[u].mx_se=max(tr[u<<1].mx_st,tr[u<<1|1].mx_se);
	}
}
void build(int u,int l,int r){
	tr[u]={l,r};
	if(l==r){
		tr[u].sum=tr[u].mx_st=tr[u].mx_hi=a[l];
		tr[u].mx_num=1;
		tr[u].mx_se=-inf;
		return;
	}
	int mid=l+r>>1;
	build(u<<1,l,mid);
	build(u<<1|1,mid+1,r);
	pushup(u);
}
void modify(int u,int val_st,int val_hi,int val_se,int val_he){
	tr[u].sum+=(tr[u].mx_num*val_st+(tr[u].r-tr[u].l+1-tr[u].mx_num)*val_se);
	tr[u].mx_hi=max(tr[u].mx_hi,tr[u].mx_st+val_hi);
	tr[u].mx_st+=val_st;
	if(tr[u].mx_se!=-inf)tr[u].mx_se+=val_se;
	tr[u].lzy_hi=max(tr[u].lzy_hi,tr[u].lzy_st+val_hi);
	tr[u].lzy_st+=val_st;
	tr[u].lzy_he=max(tr[u].lzy_he,tr[u].lzy_se+val_he);
	tr[u].lzy_se+=val_se;
}
void pushdown(int u){
	int maxx=max(tr[u<<1].mx_st,tr[u<<1|1].mx_st);
	if(tr[u<<1].mx_st==maxx)modify(u<<1,tr[u].lzy_st,tr[u].lzy_hi,tr[u].lzy_se,tr[u].lzy_he);
	else modify(u<<1,tr[u].lzy_se,tr[u].lzy_he,tr[u].lzy_se,tr[u].lzy_he);
	if(tr[u<<1|1].mx_st==maxx)modify(u<<1|1,tr[u].lzy_st,tr[u].lzy_hi,tr[u].lzy_se,tr[u].lzy_he);
	else modify(u<<1|1,tr[u].lzy_se,tr[u].lzy_he,tr[u].lzy_se,tr[u].lzy_he);
	tr[u].lzy_st=tr[u].lzy_hi=tr[u].lzy_se=tr[u].lzy_he=0;
}
void modify_add(int u,int L,int R,int v){
	if(tr[u].l>=L&&tr[u].r<=R){
		modify(u,v,v,v,v);
		return;
	}
	int mid=tr[u].l+tr[u].r>>1;
	pushdown(u);
	if(L<=mid)modify_add(u<<1,L,R,v);
	if(R>mid)modify_add(u<<1|1,L,R,v);
	pushup(u);
}
void modify_min(int u,int L,int R,int v){
	if(tr[u].mx_st<=v)return;
	if(tr[u].l>=L&&tr[u].r<=R&&tr[u].mx_se<v){
		modify(u,v-tr[u].mx_st,v-tr[u].mx_st,0,0);
		return;
	}
	int mid=tr[u].l+tr[u].r>>1;
	pushdown(u);
	if(L<=mid)modify_min(u<<1,L,R,v);
	if(R>mid)modify_min(u<<1|1,L,R,v);
	pushup(u);
}
int qry_sum(int u,int L,int R){
	if(tr[u].l>=L&&tr[u].r<=R){
		return tr[u].sum;
	}
	int mid=tr[u].l+tr[u].r>>1;
	int res=0;
	pushdown(u);
	if(L<=mid)res+=qry_sum(u<<1,L,R);
	if(R>mid)res+=qry_sum(u<<1|1,L,R);
	return res;
}
int qry_max(int u,int L,int R){
	if(tr[u].l>=L&&tr[u].r<=R){
		return tr[u].mx_st;
	}
	int mid=tr[u].l+tr[u].r>>1;
	int res=-inf;
	pushdown(u);
	if(L<=mid)res=max(res,qry_max(u<<1,L,R));
	if(R>mid)res=max(res,qry_max(u<<1|1,L,R));
	return res;
}
int qry_hi(int u,int L,int R){
	if(tr[u].l>=L&&tr[u].r<=R){
		return tr[u].mx_hi;
	}
	int mid=tr[u].l+tr[u].r>>1;
	int res=-inf;
	pushdown(u);
	if(L<=mid)res=max(res,qry_hi(u<<1,L,R));
	if(R>mid)res=max(res,qry_hi(u<<1|1,L,R));
	return res;
}
signed main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	build(1,1,n);
	while(m--){
		int op,l,r,k,v;
		cin>>op;
		if(op==1){
			cin>>l>>r>>k;
			modify_add(1,l,r,k);
		}
		else if(op==2){
			cin>>l>>r>>v;
			modify_min(1,l,r,v);
		}
		else if(op==3){
			cin>>l>>r;
			cout<<qry_sum(1,l,r)<<'\n';
		}
		else if(op==4){
			cin>>l>>r;
			cout<<qry_max(1,l,r)<<'\n';
		}
		else{
			cin>>l>>r;
			cout<<qry_hi(1,l,r)<<'\n';
		}
	}
	return 0;
}

CPU 监控

事实上,这个题的操作和上一题差不多,就是询问区间最大值,区间历史最大值和区间加,区间覆盖。

感觉要记录的东西没啥好说的,这里直接放一下:

struct node{
	int l,r,mx_st,mx_hi;
	int lzy_sta,lzy_hia;
	int lzy_stc,lzy_hic;
	node(){
		lzy_sta=lzy_hia=0;
		lzy_stc=lzy_hic=-inf;
	}
	/*
	第一行的含义同上
	lzy_sta 最大值加的数,lzy_hia 历史最大值加的数
	lzy_stc 最大值被覆盖的数,lzy_hic 历史最大值被覆盖的数 
	*/ 
}tr[N<<2];

然后我们说一下比较难且和上一题不一样的地方:

下传

先考虑区间加怎么下传。最大值和历史最大值同上一道题。然后就是懒标记的更新,首先要判断有没有区间推平,如果有对推平标记加要加的数,否则对增加标记加要加的数,代码放在下面:

void modify_add(int u,int val_st,int val_hi){
	tr[u].mx_hi=max(tr[u].mx_hi,tr[u].mx_st+val_hi);
	tr[u].mx_st+=val_st;
	if(tr[u].lzy_stc==-inf){
		tr[u].lzy_hia=max(tr[u].lzy_hia,tr[u].lzy_sta+val_hi);
		tr[u].lzy_sta+=val_st;
	}
	else{
		tr[u].lzy_hic=max(tr[u].lzy_hic,tr[u].lzy_stc+val_hi);
		tr[u].lzy_stc+=val_st;
	}
}

然后考虑区间推平怎么修改,显然非存储历史值的懒标记直接更改,存储历史值的懒标记取个最大值更改即可,代码:

void modify_cov(int u,int val_st,int val_hi){
	tr[u].mx_st=val_st;
	tr[u].mx_hi=max(tr[u].mx_hi,val_hi);
	tr[u].lzy_stc=val_st;
	tr[u].lzy_hic=max(tr[u].lzy_hic,val_hi);
}

最后就是下传。先传加的标记,直接使用上面的函数就行,最后把标记清成 0;然后更新推平,也可以用上面的函数下传,最后把标记改成极小值。

代码(合集版)

#include<bits/stdc++.h>
#define int long long
#define N 100005
#define inf 2e18
using namespace std;
int n,m,a[N];
struct node{
	int l,r,mx_st,mx_hi;
	int lzy_sta,lzy_hia;
	int lzy_stc,lzy_hic;
	node(){
		lzy_sta=lzy_hia=0;
		lzy_stc=lzy_hic=-inf;
	}
}tr[N<<2];
void pushup(int u){
	tr[u].mx_st=max(tr[u<<1].mx_st,tr[u<<1|1].mx_st);
	tr[u].mx_hi=max(tr[u<<1].mx_hi,tr[u<<1|1].mx_hi);
}
void build(int u,int l,int r){
	tr[u].l=l;tr[u].r=r;
	if(l==r){
		tr[u].mx_st=tr[u].mx_hi=a[l];
		return;
	}
	int mid=l+r>>1;
	build(u<<1,l,mid);
	build(u<<1|1,mid+1,r);
	pushup(u);
}
void modify_add(int u,int val_st,int val_hi){
	tr[u].mx_hi=max(tr[u].mx_hi,tr[u].mx_st+val_hi);
	tr[u].mx_st+=val_st;
	if(tr[u].lzy_stc==-inf){
		tr[u].lzy_hia=max(tr[u].lzy_hia,tr[u].lzy_sta+val_hi);
		tr[u].lzy_sta+=val_st;
	}
	else{
		tr[u].lzy_hic=max(tr[u].lzy_hic,tr[u].lzy_stc+val_hi);
		tr[u].lzy_stc+=val_st;
	}
}
void modify_cov(int u,int val_st,int val_hi){
	tr[u].mx_st=val_st;
	tr[u].mx_hi=max(tr[u].mx_hi,val_hi);
	tr[u].lzy_stc=val_st;
	tr[u].lzy_hic=max(tr[u].lzy_hic,val_hi);
}
void pushdown(int u){
	if(tr[u].lzy_sta||tr[u].lzy_hia){
		modify_add(u<<1,tr[u].lzy_sta,tr[u].lzy_hia);
		modify_add(u<<1|1,tr[u].lzy_sta,tr[u].lzy_hia);
		tr[u].lzy_sta=tr[u].lzy_hia=0;
	}
	if(tr[u].lzy_stc!=-inf||tr[u].lzy_hic!=-inf){
		modify_cov(u<<1,tr[u].lzy_stc,tr[u].lzy_hic);
		modify_cov(u<<1|1,tr[u].lzy_stc,tr[u].lzy_hic);
		tr[u].lzy_stc=tr[u].lzy_hic=-inf;
	}
}
void update_add(int u,int L,int R,int v){
	if(tr[u].l>=L&&tr[u].r<=R){
		modify_add(u,v,v);
		return;
	}
	int mid=tr[u].l+tr[u].r>>1;
	pushdown(u);
	if(L<=mid)update_add(u<<1,L,R,v);
	if(R>mid)update_add(u<<1|1,L,R,v);
	pushup(u);
}
void update_cov(int u,int L,int R,int v){
	if(tr[u].l>=L&&tr[u].r<=R){
		modify_cov(u,v,v);
		return;
	}
	int mid=tr[u].l+tr[u].r>>1;
	pushdown(u);
	if(L<=mid)update_cov(u<<1,L,R,v);
	if(R>mid)update_cov(u<<1|1,L,R,v);
	pushup(u);
}
int qry_max(int u,int L,int R){
	if(tr[u].l>=L&&tr[u].r<=R){
		return tr[u].mx_st;
	}
	int mid=tr[u].l+tr[u].r>>1;
	int res=-inf;
	pushdown(u);
	if(L<=mid)res=max(res,qry_max(u<<1,L,R));
	if(R>mid)res=max(res,qry_max(u<<1|1,L,R));
	return res;
}
int qry_hi(int u,int L,int R){
	if(tr[u].l>=L&&tr[u].r<=R){
		return tr[u].mx_hi;
	}
	int mid=tr[u].l+tr[u].r>>1;
	int res=-inf;
	pushdown(u);
	if(L<=mid)res=max(res,qry_hi(u<<1,L,R));
	if(R>mid)res=max(res,qry_hi(u<<1|1,L,R));
	return res;
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	build(1,1,n);
	cin>>m;
	while(m--){
		char op;
		int l,r,v;
		cin>>op;
		if(op=='Q'){
			cin>>l>>r;
			cout<<qry_max(1,l,r)<<'\n';
		}
		else if(op=='A'){
			cin>>l>>r;
			cout<<qry_hi(1,l,r)<<'\n';
		}
		else if(op=='P'){
			cin>>l>>r>>v;
			update_add(1,l,r,v);
		}
		else{
			cin>>l>>r>>v;
			update_cov(1,l,r,v);
		}
	}
	return 0;
}
posted @   zxh923  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
点击右上角即可分享
微信分享提示