线段树历史区间最值
前情提要
本来是想去打可持久化线段树的,然后发现线段树还有一个类型,就先去打这个了,没想到一打就是一周啊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;
}