线段树区间最值/区间历史最值

Still writing...

2016 年国家集训队论文

Segment Tree Beats!\(2016\) WC 交流课件。

Segment Tree Beats,吉司机线段树,由吉如一在 \(2016\) 年国家集训队论文中提出。

在本文中,\(\leftarrow\) 表示赋值。


下文中,可能会出现区间最值操作 \(\mathcal O(m\log n)\) 的复杂度(单 \(\log\)),但是似乎于 \(2025\) 年末被证伪:若存在区间最值操作,则下界为 \(\mathcal O\left(m\log^2n\right)\)

会给出原论文中的复杂度。

区间最值操作

以两道例题引入。

Gorgeous Sequence

HDU5306 Gorgeous Sequence

给定序列 \(a_1,a_2,\cdots,a_n\),维护操作:

  1. 对于 \(\forall i\in[l,r]\),令 \(a_i\leftarrow\min(a_i,x)\)
  2. 对于 \(\forall i\in[l,r]\),求 \(a_i\) 最大值。
  3. 对于 \(\forall i\in[l,r]\),求 \(a_i\) 之和。

我们将「给定 \(l,r,x\),对于 \(\forall i\in[l,r]\),令 \(a_i\leftarrow\min(a_i,x)\)」这样的操作称之为「区间最值操作」。即将区间 \([l,r]\) 内的所有数对 \(x\)\(\min\)\(\max\)

根据例题,以下仅讨论区间取 \(\min\);区间取 \(\max\) 是类似的,并且会在下一部分讨论。

传统线段树似乎无法很好的维护,每次操作中我们的操作对象都是那些大于 \(x\) 的数,而不是整个区间。那么我们考虑单独维护区间 \([l,r]\) 内的最大值 \(\textit{max}\)、最大值的个数 \(\textit{cnt}\)、次大值 \(\textit{max}_2\)。(当然,显然还需要维护区间和 \(\textit{sum}\)

这些信息都是很好上传维护的。

而区间 \([l,r]\)\(x\)\(\min\) 时:

  1. \(\textit{max}\leq x\),则取 \(\min\) 后肯定不变,无需操作。

  2. \(\textit{max}_2<x<\textit{max}\),则取 \(\min\) 后所有的 \(\textit{max}\) 都变为了 \(x\),且其余所有部分都不变。

    更新 \(\textit{sum}\) 即令 \(\textit{sum}\leftarrow\textit{sum}+\textit{cntMax}(x-\textit{max})\)

    之后令 \(\textit{max}\leftarrow x\),并且打一个懒标记 \(\textit{tagMax}\leftarrow x\) 即可。

  3. 否则,就暴力递归处理。

原论文中证明不带区间加操作的区间最值为 \(\mathcal O(m\log n)\);然而实际上似乎为 \(\mathcal O\left(m\log^2n\right)\)

参考代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
typedef long long ll;
constexpr const int N=1000000;
int n,a[N+1];
struct segTree{
    struct node{
        int l,r;
        ll sum;
        int max,max2,cnt,tag;
    }t[N<<2|1];
    
    void up(int p){
        t[p].sum=t[p<<1].sum+t[p<<1|1].sum;
        t[p].max=::max(t[p<<1].max,t[p<<1|1].max);
        if(t[p<<1].max>t[p<<1|1].max){
            t[p].max=t[p<<1].max;
            t[p].cnt=t[p<<1].cnt;
            t[p].max2=::max(t[p<<1].max2,t[p<<1|1].max);
        }else if(t[p<<1].max==t[p<<1|1].max){
            t[p].max=t[p<<1].max;
            t[p].cnt=t[p<<1].cnt+t[p<<1|1].cnt;
            t[p].max2=::max(t[p<<1].max2,t[p<<1|1].max2);
        }else{
            t[p].max=t[p<<1|1].max;
            t[p].cnt=t[p<<1|1].cnt;
            t[p].max2=::max(t[p<<1].max,t[p<<1|1].max2);
        }
    }
    void build(int p,int l,int r){
        t[p]={l,r};
        t[p].tag=-1;
        t[p].max2=-2147483648;
        if(l==r){
            t[p].sum=t[p].max=a[l];
            t[p].cnt=1;
            return;
        }
        int mid=l+r>>1;
        build(p<<1,l,mid);
        build(p<<1|1,mid+1,r);
        up(p);
    }
    void down(int p){
        if(t[p].tag!=-1){
            int max=::max(t[p<<1].max,t[p<<1|1].max);
            if(max==t[p<<1].max){
                t[p<<1].sum-=1ll*t[p<<1].cnt*(t[p<<1].max-t[p].tag);
                t[p<<1].max=t[p].tag;
                t[p<<1].tag=t[p].tag;
            }
            if(max==t[p<<1|1].max){
                t[p<<1|1].sum-=1ll*t[p<<1|1].cnt*(t[p<<1|1].max-t[p].tag);
                t[p<<1|1].max=t[p].tag;
                t[p<<1|1].tag=t[p].tag;
            }
            t[p].tag=-1;
        }
    }
    void chkmin(int p,int l,int r,int x){
        if(l<=t[p].l&&t[p].r<=r){
            if(t[p].max<=x){
                return;
            }else if(t[p].max2<x){
                t[p].sum-=1ll*t[p].cnt*(t[p].max-x);
                t[p].max=x;
                t[p].tag=x;
                return;
            }
        }
        down(p);
        if(l<=t[p<<1].r){
            chkmin(p<<1,l,r,x);
        }
        if(t[p<<1|1].l<=r){
            chkmin(p<<1|1,l,r,x);
        }
        up(p);
    }
    int max(int p,int l,int r){
        if(l<=t[p].l&&t[p].r<=r){
            return t[p].max;
        }
        down(p);
        int ans=0;
        if(l<=t[p<<1].r){
            ans=max(p<<1,l,r);
        }
        if(t[p<<1|1].l<=r){
            ans=::max(ans,max(p<<1|1,l,r));
        }
        return ans;
    }
    ll sum(int p,int l,int r){
        if(l<=t[p].l&&t[p].r<=r){
            return t[p].sum;
        }
        down(p);
        ll ans=0;
        if(l<=t[p<<1].r){
            ans=sum(p<<1,l,r);
        }
        if(t[p<<1|1].l<=r){
            ans+=sum(p<<1|1,l,r);
        }
        return ans;
    }
}t;
main(){
    /*freopen("test.in","r",stdin);
    freopen("test.out","w",stdout);*/
    
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    
    int T;
    cin>>T;
    while(T--){
        int m;
        cin>>n>>m;
        for(int i=1;i<=n;i++){
            cin>>a[i];
        }
        t.build(1,1,n);
        while(m--){
            int op,x,y,z;
            cin>>op>>x>>y;
            switch(op){
                case 0:
                    cin>>z;
                    t.chkmin(1,x,y,z);
                    break;
                case 1:
                    cout<<t.max(1,x,y)<<'\n';
                    break;
                case 2:
                    cout<<t.sum(1,x,y)<<'\n';
            }
        }
    }
    
    cout.flush();
    
    /*fclose(stdin);
    fclose(stdout);*/
    return 0;
}

最假女选手

Luogu P10639 BZOJ4695 最假女选手

维护序列 \(a_1,a_2,a_3,\cdots,a_n\),实现:

  1. 对于 \(\forall i\in[l,r]\),令 \(a_i\leftarrow a_i+x\)
  2. 对于 \(\forall i\in[l,r]\),令 \(a_i\leftarrow\max(a_i,x)\)
  3. 对于 \(\forall i\in[l,r]\),令 \(a_i\leftarrow\min(a_i,x)\)
  4. 对于 \(\forall i\in[l,r]\),求 \(a_i\) 之和。
  5. 对于 \(\forall i\in[l,r]\),求 \(a_i\) 最大值。
  6. 对于 \(\forall i\in[l,r]\),求 \(a_i\) 最小值。

同理,维护 \(\textit{sum},\textit{tagAdd},\textit{cntMax},\textit{max},\textit{max}_2,\textit{tagMax},\textit{cntMin},\textit{min},\textit{min}_2,\textit{tagMin}\),共 \(10\) 个信息。

钦定加法标记 \(\textit{tagAdd}\) 优先,则逻辑和只维护一种是一样的,只是码量会大一些。

同时,进行区间加 \(x\) 的时候要将 \(\textit{max},\textit{min}\)\(x\),如果 \(\textit{max}_2,\textit{min}_2,\textit{tagMax},\textit{tagMin}\) 存在的话也要加 \(x\)

数集重合

如果一个区间只有 $1\sim 2$ 种数,那么肯定会出现 $\textit{max}=\textit{min}$ 或 $\textit{max}_2=\textit{min},\textit{min}_2=\textit{max}$。

这种时候也要把它一起改掉。

同时,区间取 $\min$ 的时候要更新 $\textit{tagMax}$,区间取 $\max$ 也要更新 $\textit{tagMin}$。

同时存在区间加法、区间最值操作,其复杂度为 \(\mathcal O\left(m\log^2n\right)\)。(在论文中也是如此。)

参考代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
#define int ll
typedef long long ll;
constexpr const int N=5e5;
constexpr const ll inf=0x3f3f3f3f3f3f3f3f;
int n,a[N+1];
struct segTree{
	struct node{
		int l,r;
		ll sum,tagAdd;
		int cntMax,cntMin; 
		ll max,max2,tagMax;
		ll min,min2,tagMin;

		int size(){
			return r-l+1;
		}
	}t[N<<2|1];
	
	void up(int p){
		t[p].sum=t[p<<1].sum+t[p<<1|1].sum;
		if(t[p<<1].max>t[p<<1|1].max){
			t[p].max=t[p<<1].max;
			t[p].cntMax=t[p<<1].cntMax;
			t[p].max2=::max(t[p<<1].max2,t[p<<1|1].max);
		}else if(t[p<<1].max==t[p<<1|1].max){
			t[p].max=t[p<<1].max;
			t[p].cntMax=t[p<<1].cntMax+t[p<<1|1].cntMax;
			t[p].max2=::max(t[p<<1].max2,t[p<<1|1].max2);
		}else{
			t[p].max=t[p<<1|1].max;
			t[p].cntMax=t[p<<1|1].cntMax;
			t[p].max2=::max(t[p<<1].max,t[p<<1|1].max2);
		}
		
		if(t[p<<1].min<t[p<<1|1].min){
			t[p].min=t[p<<1].min;
			t[p].cntMin=t[p<<1].cntMin;
			t[p].min2=::min(t[p<<1].min2,t[p<<1|1].min);
		}else if(t[p<<1].min==t[p<<1|1].min){
			t[p].min=t[p<<1].min;
			t[p].cntMin=t[p<<1].cntMin+t[p<<1|1].cntMin;
			t[p].min2=::min(t[p<<1].min2,t[p<<1|1].min2);
		}else{
			t[p].min=t[p<<1|1].min;
			t[p].cntMin=t[p<<1|1].cntMin;
			t[p].min2=::min(t[p<<1].min,t[p<<1|1].min2);
		}
	}
	void build(int p,int l,int r){
		t[p]={l,r};
		t[p].tagMax=-inf;
		t[p].tagMin=inf;
		if(l==r){
			t[p].sum=t[p].max=t[p].min=a[l];
			t[p].max2=-inf,t[p].min2=inf;
			t[p].cntMax=t[p].cntMin=1;
			return;
		}
		int mid=l+r>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
		up(p);
	}
	void downAdd(int p,ll x){
		t[p].sum+=t[p].size()*x;
		t[p].tagAdd+=x;
		t[p].max+=x;
		if(t[p].max2!=-inf){
			t[p].max2+=x;
		}
		if(t[p].tagMax!=-inf){
			t[p].tagMax+=x;
		}
		t[p].min+=x;
		if(t[p].min2!=inf){
			t[p].min2+=x;
		}
		if(t[p].tagMin!=inf){
			t[p].tagMin+=x;
		}
	}
	void downMax(int p,ll x){
		if(t[p].min>x){
			return;
		}
		t[p].sum+=t[p].cntMin*(x-t[p].min);
		t[p].tagMax=x;
		if(t[p].max2==t[p].min){
			t[p].max2=x;
		}
		if(t[p].max==t[p].min){
			t[p].max=x;
		}
		t[p].min=x;
		if(t[p].tagMin<x){
			t[p].tagMin=x;
		}
	}
	void downMin(int p,ll x){
		if(t[p].max<=x){
			return;
		}
		t[p].sum+=t[p].cntMax*(x-t[p].max);
		t[p].tagMin=x;
		if(t[p].min2==t[p].max){
			t[p].min2=x;
		}
		if(t[p].min==t[p].max){
			t[p].min=x;
		}
		t[p].max=x;
		if(t[p].tagMax>x){
			t[p].tagMax=x;
		}
	}
	void down(int p){
		if(t[p].tagAdd){
			downAdd(p<<1,t[p].tagAdd);
			downAdd(p<<1|1,t[p].tagAdd);
			t[p].tagAdd=0;
		}
		if(t[p].tagMax!=-inf){
			downMax(p<<1,t[p].tagMax);
			downMax(p<<1|1,t[p].tagMax);
			t[p].tagMax=-inf;
		}
		if(t[p].tagMin!=inf){
			downMin(p<<1,t[p].tagMin);
			downMin(p<<1|1,t[p].tagMin);
			t[p].tagMin=inf;
		}
	}
	void add(int p,int l,int r,int x){
		if(l<=t[p].l&&t[p].r<=r){
			downAdd(p,x); 
			return; 
		}
		down(p);
		if(l<=t[p<<1].r){
			add(p<<1,l,r,x);
		}
		if(t[p<<1|1].l<=r){
			add(p<<1|1,l,r,x);
		}
		up(p);
	}
	void chkmax(int p,int l,int r,int x){
		if(l<=t[p].l&&t[p].r<=r){
			if(x<=t[p].min){
				return;
			}else if(x<t[p].min2){
				downMax(p,x);
				return;
			}
		}
		down(p);
		if(l<=t[p<<1].r){
			chkmax(p<<1,l,r,x);
		}
		if(t[p<<1|1].l<=r){
			chkmax(p<<1|1,l,r,x);
		}
		up(p);
	}
	void chkmin(int p,int l,int r,int x){
		if(l<=t[p].l&&t[p].r<=r){
			if(t[p].max<=x){
				return;
			}else if(t[p].max2<x){
				downMin(p,x);
				return;
			}
		}
		down(p);
		if(l<=t[p<<1].r){
			chkmin(p<<1,l,r,x);
		}
		if(t[p<<1|1].l<=r){
			chkmin(p<<1|1,l,r,x);
		}
		up(p);
	}
	ll sum(int p,int l,int r){
		if(l<=t[p].l&&t[p].r<=r){
			return t[p].sum;
		}
		down(p);
		ll ans=0;
		if(l<=t[p<<1].r){
			ans=sum(p<<1,l,r);
		}
		if(t[p<<1|1].l<=r){
			ans+=sum(p<<1|1,l,r);
		}
		return ans;
	}
	ll max(int p,int l,int r){
		if(l<=t[p].l&&t[p].r<=r){
			return t[p].max;
		}
		down(p);
		ll ans=-inf;
		if(l<=t[p<<1].r){
			ans=max(p<<1,l,r);
		}
		if(t[p<<1|1].l<=r){
			ans=::max(ans,max(p<<1|1,l,r));
		}
		return ans;
	}
	ll min(int p,int l,int r){
		if(l<=t[p].l&&t[p].r<=r){
			return t[p].min;
		}
		down(p);
		ll ans=inf;
		if(l<=t[p<<1].r){
			ans=min(p<<1,l,r);
		}
		if(t[p<<1|1].l<=r){
			ans=::min(ans,min(p<<1|1,l,r));
		}
		return ans;
	}
}t;
main(){
	/*freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);*/
	
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	t.build(1,1,n);
	int m;
	cin>>m;
	while(m--){
		int op,l,r,x;
		cin>>op>>l>>r;
		ll pl; 
		switch(op){
			case 1:
				cin>>x;
				t.add(1,l,r,x);
				break;
			case 2:
				cin>>x;
				t.chkmax(1,l,r,x);
				break;
			case 3:
				cin>>x;
				t.chkmin(1,l,r,x);
				break;
			case 4:
				cout<<t.sum(1,l,r)<<'\n';
				break;
			case 5:
				cout<<t.max(1,l,r)<<'\n';
				break;
			case 6:
				cout<<t.min(1,l,r)<<'\n';
				break;
		}
	}
	
	cout.flush();
	
	/*fclose(stdin);
	fclose(stdout);*/
	return 0;
}

区间最值转化为区间加减

本质上,我们的做法都是将区间取 \(\min\) 转化为了对区间最大值的加减,将区间内的数划分为了最值、非最值来分别维护。

区间历史最值

相关定义

  • 历史最大值:\(a_i\) 出现过的最大值。
  • 历史最小值:\(a_i\) 出现过的最小值。
  • 历史版本和:\(a_i\) 所有版本的和。

可以用懒标记处理的问题

Luogu P4314 CPU 监控

给定序列 \(a_1,a_2,\cdots,a_n\),设 \(b_1,b_2,\cdots,b_n\) 为其历史最大值,维护操作:

  1. 对于 \(\forall i\in[l,r]\),求 \(a_i\) 最大值。
  2. 对于 \(\forall i\in[l,r]\),求 \(b_i\) 最大值。
  3. 对于 \(\forall i\in[l,r]\),令 \(a_i\leftarrow a_i+x\)
  4. 对于 \(\forall i\in[l,r]\),令 \(a_i\leftarrow x\)

先不考虑赋值操作。

那么维护区间最大值 \(\textit{max}\),历史最大值 \(\textit{hmax}\),加法标记 \(\textit{add}\) 是容易想到的。

维护 \(\textit{max}\) 时简单的,直接加上 \(\textit{add}\) 即可;考虑如何求解 \(\textit{hmax}\)

线段树懒标记的特性决定了懒标记会在一段时间(生存周期)内「停留」在一个位置不变,同时子树内所有信息不变。那么我们记录生存周期内当前区间最大的 \(\textit{add}\)\(\textit{hadd}\),则在下传更新时有:

\[\textit{hmax}=\max(\textit{hmax},\textit{max}+\textit{hadd}) \]

维护 \(\textit{hadd}\) 也不难,\(\textit{hadd}\leftarrow\max(\textit{hadd},\textit{add}+hx)\) 即可。\(\textit{hx}\) 表示增加 \(x\) 时,上一层在一个生存周期内增加的最大值。

对于区间覆盖,这其实很简单。因为覆盖之后值都是一样的,反而很好维护。

时间复杂度 \(\mathcal O(m\log n)\)。(没有区间最值操作,\(\mathcal O(m\log n)\) 是正确的。)

参考代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
typedef long long ll;
constexpr const int N=1e5,inf=2147483647;
int n,a[N+1];
struct segTree{
	struct node{
		int l,r;
		int max,hmax;
		int add,hadd;
		int set,hset;
	}t[N<<2|1];
	
	void up(int p){
		t[p].max=::max(t[p<<1].max,t[p<<1|1].max);
		t[p].hmax=::max(t[p<<1].hmax,t[p<<1|1].hmax);
	}
	void build(int p,int l,int r){
		t[p]={l,r};
		t[p].set=t[p].hset=-inf;
		if(l==r){
			t[p].max=t[p].hmax=a[l];
			return;
		}
		int mid=l+r>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
		up(p);
	}
	void downAdd(int p,int x,int hx){
		t[p].hmax=::max(t[p].hmax,t[p].max+hx);
		t[p].max+=x;
		if(t[p].set==-inf){
			t[p].hadd=::max(t[p].hadd,t[p].add+hx);
			t[p].add+=x;
		}else{
			t[p].hset=::max(t[p].hset,t[p].set+hx);
			t[p].set+=x; 
		}
	}
	void downSet(int p,int x,int hx){
		t[p].hmax=::max(t[p].hmax,hx);
		t[p].max=x;
		t[p].hset=::max(t[p].hset,hx);
		t[p].set=x;
	}
	void down(int p){
		if(t[p].add||t[p].hadd){
			downAdd(p<<1,t[p].add,t[p].hadd);
			downAdd(p<<1|1,t[p].add,t[p].hadd);
			t[p].add=t[p].hadd=0;
		}
		if(t[p].set!=-inf||t[p].hset!=-inf){
			downSet(p<<1,t[p].set,t[p].hset);
			downSet(p<<1|1,t[p].set,t[p].hset);
			t[p].set=t[p].hset=-inf;
		}
	}
	int max(int p,int l,int r){
		if(l<=t[p].l&&t[p].r<=r){
			return t[p].max;
		}
		int ans=-inf;
		down(p);
		if(l<=t[p<<1].r){
			ans=max(p<<1,l,r);
		}
		if(t[p<<1|1].l<=r){
			ans=::max(ans,max(p<<1|1,l,r));
		}
		return ans;
	}
	int hmax(int p,int l,int r){
		if(l<=t[p].l&&t[p].r<=r){
			return t[p].hmax;
		}
		int ans=-inf;
		down(p);
		if(l<=t[p<<1].r){
			ans=hmax(p<<1,l,r);
		}
		if(t[p<<1|1].l<=r){
			ans=::max(ans,hmax(p<<1|1,l,r));
		}
		return ans;
	}
	void add(int p,int l,int r,int x){
		if(l<=t[p].l&&t[p].r<=r){
			downAdd(p,x,::max(x,0));
			return;
		}
		down(p);
		if(l<=t[p<<1].r){
			add(p<<1,l,r,x);
		}
		if(t[p<<1|1].l<=r){
			add(p<<1|1,l,r,x);
		}
		up(p);
	}
	void set(int p,int l,int r,int x){
		if(l<=t[p].l&&t[p].r<=r){
			downSet(p,x,x);
			return;
		}
		down(p);
		if(l<=t[p<<1].r){
			set(p<<1,l,r,x);
		}
		if(t[p<<1|1].l<=r){
			set(p<<1|1,l,r,x);
		}
		up(p);
	}
}t;
int main(){
	/*freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);*/
	
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	t.build(1,1,n);
	int m;
	cin>>m;
	while(m--){
		char op;
		int l,r,x;
		cin>>op>>l>>r;
		switch(op){
			case 'Q':
				cout<<t.max(1,l,r)<<'\n';
				break;
			case 'A':
				cout<<t.hmax(1,l,r)<<'\n';
				break;
			case 'P':
				cin>>x;
				t.add(1,l,r,x);
				break;
			case 'C':
				cin>>x;
				t.set(1,l,r,x);
				break;
		}
	}
	
	cout.flush();
	
	/*fclose(stdin);
	fclose(stdout);*/
	return 0;
}

无法用懒标记处理的单点问题

此处「单点问题」除查询单点历史最值外,也包括查询区间历史最小值的最小值、查询区间历史最大值的最大值。因为这两个区间询问的处理方法与单点询问完全相同,所以把它们归入单点问题之中。

UOJ169 元旦老人与数列

设序列 \(a_1,a_2,\cdots,a_n\)\(b_1,b_2,\cdots,b_n\) 为其历史最小值;维护操作:

  1. 对于 \(\forall i\in[l,r]\),令 \(a_i\leftarrow a_i+x\)
  2. 对于 \(\forall i\in[l,r]\),令 \(a_i\leftarrow\max(a_i,x)\)
  3. 对于 \(\forall i\in[l,r]\),求 \(a_i\) 最小值。
  4. 对于 \(\forall i\in[l,r]\),求 \(b_i\) 最小值。

无法运用懒标记维护,因为取 \(\max\) 与历史最小值方向相反,信息无法合并。

但是可以直接运用区间最值操作,将其转化为区间加减后可以类似于上文维护历史最小值的最小值。

时间复杂度 \(\mathcal O\left(m\log^2n\right)\)

无区间最值操作的区间问题

历史最小值之和

对于序列 \(a_1,a_2,\cdots,a_n\),设其历史最小值为 \(b_1,b_2,\cdots,b_n\);定义 \(c_i=a_i-b_i\)

\(a_i\leftarrow a_i+x\) 时:

  • \(b_i\) 不变,则令 \(c_i\leftarrow c_i+x\)
  • \(b_i\) 变为了 \(a_i\),则令 \(c_i\leftarrow0\)

这也就等价于让 \(c_i\) 先加 \(x\),再与 \(0\)\(\max\),即 \(c_i\leftarrow\max(c_i+x,0)\)

这样就可以 \(\mathcal O\left(m\log^2n\right)\) 维护 \(a,c\)。查询 \(b\) 时,二者相减即可得到。

历史最大值之和

同理,对于序列 \(a_1,a_2,\cdots,a_n\),设其历史最小值为 \(b_1,b_2,\cdots,b_n\);定义 \(c_i=a_i-b_i\)

\(a_i\leftarrow a_i+x\),则 \(c_i\leftarrow\min(c_i+x,0)\)

时间复杂度 \(\mathcal O\left(m\log^2n\right)\)

历史版本和之和

设序列 \(a_1,a_2,\cdots,a_n\) 的历史版本和为 \(b_1,b_2,\cdots,b_n\),当前版本号为 \(t\)

定义 \(c_i=b_i-t\cdot a_i\)

此时,\(a_i\leftarrow a_i+x\) 即令 \(c_i\leftarrow c_i-t\cdot x\)

因此可以 \(\mathcal O(m\log n)\) 维护 \(c\)

有区间最值操作的区间问题

先将区间最值操作转化为区间加减,再通过上一节的方法写即可。

posted @ 2026-02-05 20:09  TH911  阅读(30)  评论(0)    收藏  举报