线段树历史区间最值

发布时间 2023-04-08 22:08:13作者: k_stefani

前情提要

本来是想去打可持久化线段树的,然后发现线段树还有一个类型,就先去打这个了,没想到一打就是一周啊QAQ。

P6242 【模板】线段树 3

  • 1 l r k:对于所有的 \(i\in[l,r]\),将 \(A_i\) 加上 \(k\)\(k\) 可以为负数)。
  • 2 l r v:对于所有的 \(i\in[l,r]\),将 \(A_i\) 变成 \(\min(A_i,v)\)
  • 3 l r:求 \(\sum_{i=l}^{r}A_i\)
  • 4 l r:对于所有的 \(i\in[l,r]\),求 \(A_i\) 的最大值。
  • 5 l r:对于所有的 \(i\in[l,r]\),求 \(B_i\) 的最大值。

在每一次操作后,我们都进行一次更新,让 \(B_i\gets\max(B_i,A_i)\)

重点看第 \(2\) 个操作和第 \(5\) 个操作。

操作 \(2\)

关于第 \(2\) 个操作,我们定义 \(3\) 个变量来维护:

  • \(num\) 表示该区间中最大数的个数。

  • \(max\) 表示该区间中的最大数。

  • \(se\) 表示该区间中的严格次大数。

这样定义的意义在于当我们在线段树中找到一段区间使得 \(se \le v \le max\),需要更改的数据只有 \(max\) 以及 \(sum\)

\(sum(p)-=num(p)*(max(p)-v)\)

\(max(p)=v\)

假设在没有操作 \(1\) 的影响下,懒惰标记(\(addtag\)),更新操作为以下代码:

void addtag(int p,int v){
	if(v>=max_dat(p)) return;
   //说明此时该区间没有被更新的必要。
   //可能是他的父区间没有被更新。
   //也可能父区间的最大值不在该区间中。
	sum(p)-=num(p)*(max_dat(p)-v);
	max_dat(p)=v;
	return;
}

void spread(int p){
	addtag(p*2,max(p));
   addtag(p*2+1,max(p));
	return;
}

if(type==2){
	if(v>=max(p)) return;//此时没有更新的必要
  	else if(v>=se(p)){
    //此时只需要更新最大值
		addtag(p,v);
		return;
	}
   else{
   	spread(p);
   	继续往下查找。
   }

{

但当操作 \(1\) 加入进来时,这样更新就行不通了。因为我们不知道此时的最大值是被操作 \(2\) 更新变小的,还是操作 \(1\) 更新变小的。假设原来原本父区间的最大值是大于子区间的最大值的。在操作 \(1\) 的影响下作了减法之后,若此时父区间最大值小于子区间最大值,就会将子区间最大值更新。此时是错误的。

解决方法是将对最大值的更改标记(\(addmax\))和对非最大值(\(addtag\))的更改标记分开来讨论。

对于最大值来说:

  • 操作 \(1\) : \(addmax(p) += k\)

  • 操作 \(2\) : \(addmax(p)-=(max(p)-v)\)

  • \(spread\) 时 :

当父区间最大值与子区间最大值相等:\(max(p*2)+=addmax(p)\) (\(p*2+1\) 同理)。

否则 : \(max(p*2)+=addtag(p)\)

更新 \(sum\) 时,也将最大值和非最大值的改变分开来讨论。

\(sum(p*2)+=num(p*2+1)*k1 + (r(p)-l(p)+1-num(p*2))*addtag(p)\)

这里的 \(k1\) 指的是区间 \(p*2\) 的最大值的改变,不能直接套 \(addmax(p)\),要分类讨论。当子区间最大值等于父区间原本的最大值时,用 \(addmax(p)\) 更新,否则就用 \(addtag(p)\) 更新。

操作 \(5\)

对于实时更新最大值的父区间来说很简单,也可以实时更新历史最大值(\(hadmax\))。但对于被延时更新的子区间来说,不能用最大值加上懒惰标记来更新,因为懒惰标记有可能是结合了好几个 \(1,2\) 操作的,需要用到子区间时才统一更新,此时历史最大值有可能在过程中产生

因此我们还需要增加两个变量,一个用来维护 \(addmax\) 在过程中的最大值,一个用来维护 \(addtag\) 在过程中的最大值。于是更新就成为了:

\(hadmax(p*2)=\max(max(p*2),max(p*2)+k2)\)

此时保证 \(max(p*2)+k2\) 是过程中的最大值。

这里的 \(k2\) 也要分类讨论子区间最大值是否与父区间原来最大值相同,是 \(k2\) 就是 \(addmax\) 在过程中的最大值,否则就是 \(addtag\) 在过程中的最大值。

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#define min_num -0x7FFFFFFF
#define ll long long
using namespace std;
ll n,m;
struct node{
	ll l,r,max_dat,had_max,se_dat,add_max,max_tag,add_tag,num,max_add_tag;
	ll sum;
	#define l(x) t[x].l
	#define r(x) t[x].r
	#define sum(x) t[x].sum
	#define max_dat(x) t[x].max_dat
	#define se_dat(x) t[x].se_dat
	#define add_max(x) t[x].add_max
	#define max_tag(x) t[x].max_tag//addmax 过程中最大值
	#define add_tag(x) t[x].add_tag
	#define max_add_tag(x) t[x].max_add_tag//addtag 过程中最大值
	#define had_max(x) t[x].had_max
	#define num(x) t[x].num
}t[500005*4];
ll a[500005];
ll ans_sum,max_ans;
void replace(ll p){//通过子区间更新父区间
	max_dat(p)=max(max_dat(p*2),max_dat(p*2+1));
	had_max(p)=max(had_max(p*2),had_max(p*2+1));
	sum(p)=sum(p*2)+sum(p*2+1);
	if(max_dat(p*2)==max_dat(p*2+1)){
		se_dat(p)=max(se_dat(p*2+1),se_dat(p*2));
		num(p)=num(p*2)+num(p*2+1);
	}
	else{
		se_dat(p)=max(se_dat(p*2),se_dat(p*2+1));
		se_dat(p)=max(se_dat(p),min(max_dat(p*2),max_dat(p*2+1)));
		if(max_dat(p*2)>max_dat(p*2+1)) num(p)=num(p*2);
		else num(p)=num(p*2+1);
	}
	return;
}
void build(ll p,ll l,ll r){
	l(p)=l,r(p)=r;
	if(l==r){
		had_max(p)=sum(p)=max_dat(p)=a[l];
		se_dat(p)=min_num;
		num(p)=1;
		return;
	}
	ll mid=(l+r)/2;
	build(p*2,l,mid);
	build(p*2+1,mid+1,r);
	had_max(p)=min_num;
	replace(p);
	return;
}
void spread(ll p,ll k1,ll k2,ll k3,ll k4){
	//k1k2与最大值更改相关,k3k4则是与非最大值更改相关
	sum(p)+=k1*num(p)+k3*(r(p)-l(p)+1-num(p));
	//一定要先更新历史最大值,再更新最大值
	had_max(p)=max(had_max(p),max_dat(p)+k2);
	max_dat(p)+=k1;
	if(se_dat(p)!=min_num) se_dat(p)+=k3;	
	//标记更新也要注意顺序!	
    	max_tag(p)=max(max_tag(p),add_max(p)+k2),add_max(p)+=k1;
	max_add_tag(p)=max(max_add_tag(p),add_tag(p)+k4),add_tag(p)+=k3;
	return;
}
void ask(ll p){
	ll maxn=max(max_dat(p*2),max_dat(p*2+1));
    //最大值分类讨论是否与原本父区间最大值相同
	if(maxn==max_dat(p*2)) spread(p*2,add_max(p),max_tag(p),add_tag(p),max_add_tag(p));
	else spread(p*2,add_tag(p),max_add_tag(p),add_tag(p),max_add_tag(p));
	if(maxn==max_dat(p*2+1)) spread(p*2+1,add_max(p),max_tag(p),add_tag(p),max_add_tag(p));
	else spread(p*2+1,add_tag(p),max_add_tag(p),add_tag(p),max_add_tag(p));
	add_max(p)=max_tag(p)=add_tag(p)=max_add_tag(p)=0;
	return;
}
void f(ll p,ll l,ll r,ll type,ll num){
	if(l<=l(p)&&r>=r(p)){
		if(type==1){
			sum(p)+=(r(p)-l(p)+1)*num;
			add_tag(p)+=num;
			add_max(p)+=num;
			max_dat(p)+=num;
			if(se_dat(p)!=min_num) se_dat(p)+=num;
			had_max(p)=max(had_max(p),max_dat(p));
			max_add_tag(p)=max(max_add_tag(p),add_tag(p));
			max_tag(p)=max(max_tag(p),add_max(p));
			return;
		}
		if(type==2){
			if(num>max_dat(p)) return;
			else if(num>=se_dat(p)){
				ll k=max_dat(p)-num;
				add_max(p)-=k;
				sum(p)-=k*num(p);
				max_dat(p)=num;
				return;
			}
			else{
				ask(p);
				ll mid=(l(p)+r(p))/2;
				if(l<=mid) f(p*2,l,r,type,num);
				if(r>mid) f(p*2+1,l,r,type,num);
				replace(p);
				return;
			}
		}
		if(type==3){
			ans_sum+=sum(p);
			return;
		}
		if(type==4){
			max_ans=max(max_ans,max_dat(p));
			return;
		}
		if(type==5){
			max_ans=max(max_ans,had_max(p));
			return;
		}
	}
	ask(p);
	ll mid=(r(p)+l(p))/2;
	if(l<=mid) f(p*2,l,r,type,num);
	if(r>mid) f(p*2+1,l,r,type,num);
	replace(p);
	return;
}

int main(){
	//freopen("P6242_6.in","r",stdin);
	//freopen("ans.txt","w",stdout);
	scanf("%lld%lld",&n,&m);
	for(ll i=1;i<=n;i++) scanf("%lld",&a[i]);
	build(1,1,n);
	for(ll i=1;i<=m;i++){
		ll type,l,r;
		scanf("%lld%lld%lld",&type,&l,&r);
	//	cout<<i<<" "<<type<<" "<<l<<" "<<r<<endl;
		if(type==1){
			ll k;
			scanf("%lld",&k);
			f(1,l,r,type,k);
		}
		if(type==2){
			ll v;
			scanf("%lld",&v);
			f(1,l,r,type,v);
		}
		if(type==3){
			ans_sum=0;
			f(1,l,r,type,0);
			cout<<ans_sum<<endl;
		}
		if(type==4||type==5){
			//cout<<min_num<<endl;
			max_ans=min_num;
			f(1,l,r,type,0);
			cout<<max_ans<<endl;
		}
	} 
	return 0;
}