$#C240215A. 线段树3


$#C240215A. 线段树3

标签(Label)

  • 线段树
  • 模板

网址(Website)

$\qquad$P6242 【模板】线段树 3(区间最值操作、区间历史最值)

题目(Problem)

题目背景

$\qquad$吉司机线段树。

题目描述

$\qquad$给出一个长度为 $n$ 的数列 $A$,同时定义一个辅助数组 $B$,$B$ 开始与 $A$ 完全相同。接下来进行了 $m$ 次操作,操作有五种类型,按以下格式给出:

  • 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$ 的最大值。

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

输入格式

$\qquad$第一行包含两个正整数 $n,m$,分别表示数列 $A$ 的长度和操作次数。

$\qquad$第二行包含 $n$ 个整数 $A_1,A_2,\cdots,A_n$,表示数列 $A$。

$\qquad$接下来 $m$ 行,每行行首有一个整数 $op$,表示操作类型;接下来两个或三个整数表示操作参数,格式见【题目描述】。

输出格式

$\qquad$对于 $op\in{3,4,5}$ 的操作,输出一行包含一个整数,表示这个询问的答案。

样例

输入

5 6
1 2 3 4 5
3 2 5
1 1 3 3
4 2 4
2 3 4 1
5 1 5
3 1 4

输出

14
6
6
11

提示

样例说明

操作次数输入内容操作数列输出结果
0$1,2,3,4,5$
13 2 5求出 $[2,5]$ 所有数的和$1,2,3,4,5$14
21 1 3 3将 $[1,3]$ 内所有数加 $3$$4,5,6,4,5$
34 2 4求出 $[2,4]$ 所有数的最大值$4,5,6,4,5$6
42 3 4 1将 $[3,4]$ 所有数与 $1$ 取最小值$4,5,1,1,5$
55 1 5求出 $[1,5]$ 所有位置历史最大值的最大值$4,5,1,1,5$6
63 1 4求出 $[1,4]$ 所有数的和$4,5,1,1,5$11

数据规模与约定

  • 对于测试点 $1,2$,满足 $n,m\leq 5000$;
  • 对于测试点 $3,4$,满足 $op\in{1,2,3,4}$;
  • 对于测试点 $5,6$,满足 $op\in{1,3,4,5}$;
  • 对于全部测试数据,保证 $1\leq n,m\leq 5\times 10^5$,$-5\times10^8\leq A_i\leq 5\times10^8$,$op\in[1,5]$,$1 \leq l\leq r \leq n$,$-2000\leq k\leq 2000$,$-5\times10^8\leq v\leq 5\times10^8$。

提示

本题输入量较大,请使用合理高效的读入方法。

题解(Solution)

思路:

$\qquad$这道题是吉司机线段树的模板。

$\qquad$对于加法,直接修改即可。

$\qquad$对于取最小值,由于对区间更新最小值会影响到该区间的 $sum$ ,且不好判断,因此我们选择记录最大值及次大值,如果此时取最小值的 $v$ 满足在最大值和次大值之间,那么我们就只用修改最大值,并且更新 $sum$ 就好了。

$\qquad$考虑求 $b[i]$ 数组,由于记录的是历史的最大值,所以我们需要用一个值来记录 pushdown 下传的值和其下传的历史最大值。

$\qquad$一开始时,我构思的是通过修改 add 操作对 min 操作的影响来实现修改,但是最后发现在 pushdown 的时候会出现问题—— add 操作和 min 操作的先后会影响到 $hmax$ (即历史最大值)的值。(例如 add(1,n,5),min(1,n,2)min(1,n,2),add(1,n,5) ,虽然对于 $sum$ 值的结果不会变(add 可以直接加在 to_min 上),但是这个先后顺序在进行 pushdown 时会被无视,导致历史值变大。

$\qquad$PS:没看懂的可以去 $hack$ 一下我 $50pts$ 的代码(在后面),找一下问题。

$\qquad$而正解是什么呢?我们可以直接把 min 操作,转换为单点修改当前区间的最大值(大概是这个意思),将加减操作分解为对当前的最大值的修改和对其余值的修改,那么当我们找到一个区间满足要取最小值的 $v$ 在其最大值与次大值之间时,就可以只对其最大值进行修改,而不会影响其他值。

总的来说:

需要这些变量:

  • $sum$ :当前区间的和
  • $siz$ :当前区间的个数(即 $r-l+1$ )
  • $mx1$ :当前区间的最大值
  • $mx2$:当前区间的次大值
  • $cmx$:当前区间最大值的个数
  • $hmx$:当前区间历史最大值
  • $ad1$:最大值的加减懒标记
  • $ad2$:除了最大值以外的值的加减懒标记
  • $had1$:最大值的历史最大加减懒标记
  • $had2$:除了最大值以外的值的历史最大加减懒标记

上传:

注意最大值的更新与最大值个数的记录:

void pushup(Tr &T,Tr L,Tr R){
	T.sum=L.sum+R.sum;
	T.mx1=max(L.mx1,R.mx1);
	if(L.mx1==R.mx1){
		T.mx2=max(L.mx2,R.mx2);
		T.cmx=L.cmx+R.cmx;
	}else{
		T.mx2=max(min(L.mx1,R.mx1),max(L.mx2,R.mx2));
		T.cmx=L.mx1>R.mx1?L.cmx:R.cmx;
	}T.hmx=max(L.hmx,R.hmx);
//	cerr<<"PUSHUP:"<<T.sum<<' '<<T.mx1<<' '<<T.mx2<<' '<<T.hmx<<'\n';
}

建树:

代码如下,正常建树就可以了:

#define mid (l+r>>1)
#define ls (p<<1)
#define rs (p<<1|1) 
struct Tr{
	int sum,siz,cmx;
	int mx1,mx2,hmx;
	int ad1,had1,ad2,had2;
}tr[N<<2|3];

void build(int p,int l,int r){
	tr[p]={a[l],r-l+1,1,a[l],-inf,a[l],0,0,0,0};
	if(l==r) return;
	build(ls,l,mid),build(rs,mid+1,r);
	pushup(tr[p],tr[ls],tr[rs]);
}

下传:

下传时先看修改:

void modify(int p,int k1,int hk1,int k2,int hk2){
	tr[p].sum += (tr[p].cmx * k1 + (tr[p].siz - tr[p].cmx)*k2);
	tr[p].hmx = max(tr[p].hmx, tr[p].mx1 + hk1);
	tr[p].mx1 += k1;
	
	if(tr[p].mx2!=-inf) tr[p].mx2+=k2;
	
	tr[p].had1 = max(tr[p].had1, tr[p].ad1 + hk1);
	tr[p].ad1 += k1;
	tr[p].had2 = max(tr[p].had2, tr[p].ad2 + hk2);
	tr[p].ad2 += k2;
}

pushdown 可以判断最大值在哪里,从而跟踪修改,如果这个区间不是最大值,那么直接下传其余值的标记,代码如下:

void pushdown(int p){
	int maxn = max(tr[ls].mx1, tr[rs].mx1);
	if(tr[ls].mx1 == maxn)
		modify(ls,tr[p].ad1,tr[p].had1,tr[p].ad2,tr[p].had2);
	else modify(ls,tr[p].ad2,tr[p].had2,tr[p].ad2,tr[p].had2);
	
	if(tr[rs].mx1 == maxn)
		modify(rs,tr[p].ad1,tr[p].had1,tr[p].ad2,tr[p].had2);
	else modify(rs,tr[p].ad2,tr[p].had2,tr[p].ad2,tr[p].had2);
		
	tr[p].ad1 = tr[p].had1 = tr[p].ad2 = tr[p].had2 = 0;
}

更新:

直接上代码:

void update_sum(int p,int l,int r,int L,int R,int k){
	if(L<=l && r<=R) return modify(p,k,k,k,k);
	pushdown(p);
    if(L<=mid) update_sum(ls,l,mid,L,R,k);
    if(R>mid) update_sum(rs,mid+1,r,L,R,k);
    pushup(tr[p],tr[ls],tr[rs]);
}

void update_min(int p,int l,int r,int L,int R,int v){
	if(tr[p].mx1<=v) return;
	if(L<=l && r<=R && tr[p].mx2<v)
		return modify(p,v-tr[p].mx1,v-tr[p].mx1,0,0);
	pushdown(p);
	if(L<=mid) update_min(ls,l,mid,L,R,v);
	if(R>mid) update_min(rs,mid+1,r,L,R,v);
	pushup(tr[p],tr[ls],tr[rs]);
}

询问:

这里直接一次搞定,可能需要理解一下这种打法:

Tr ask(int p,int l,int r,int L,int R){
	if(L<=l && r<=R) return tr[p];pushdown(p);
	if(R<=mid) return ask(ls,l,mid,L,R);
	if(L>mid) return ask(rs,mid+1,r,L,R);
	Tr T;return pushup(T,ask(ls,l,mid,L,mid),ask(rs,mid+1,r,mid+1,R)),T;
}

代码(Code)

50pts

#include<bits/stdc++.h>
#define For(i,l,r) for(int i=l;i<=r;i++)
#define Rof(i,l,r) for(int i=l;i>=r;i--)
using namespace std;
#define int long long
inline int input(){int x;return cin>>x,x;}
const int inf = 0x3f3f3f3f3f3f3f3f;
const int N = 501234;

int n,m,a[N];

#define mid (l+r>>1)
#define ls (p<<1)
#define rs (p<<1|1) 
struct Tr{
	int sum,siz;
	int mx1,mx2,cmx;
	int add,had,tmn;
	int hmx;
}tr[N<<2|3];

void pushup(Tr &T,Tr L,Tr R){
	T.sum=L.sum+R.sum;
	T.mx1=max(L.mx1,R.mx1);
	if(L.mx1==R.mx1){
		T.mx2=max(L.mx2,R.mx2);
		T.cmx=L.cmx+R.cmx;
	}else{
		T.mx2=max(min(L.mx1,R.mx1),max(L.mx2,R.mx2));
		T.cmx=L.mx1>R.mx1?L.cmx:R.cmx;
	}T.hmx=max(L.hmx,R.hmx);
}

void build(int p,int l,int r){
	tr[p]={a[l],r-l+1,a[l],-inf,1,0,0,inf,a[l]};
	if(l==r) return;
	build(ls,l,mid),build(rs,mid+1,r);
	pushup(tr[p],tr[ls],tr[rs]);
}

void push_add(int p,int k,int hk){
	tr[p].hmx=max(tr[p].hmx,tr[p].mx1+hk);
	tr[p].had=max(tr[p].had,tr[p].add+hk),
	
	tr[p].sum+=tr[p].siz*k;
	tr[p].mx1+=k,tr[p].mx2+=tr[p].mx2==-inf?0:k;
	tr[p].add+=k,tr[p].tmn+=tr[p].tmn==inf?0:k;
}

void push_min(int p,int v){
	if(tr[p].mx1 <= v) return;
	tr[p].sum+=(v-tr[p].mx1)*tr[p].cmx;
	tr[p].mx1=tr[p].tmn=v;
}

void pushdown(int p){
	if(tr[p].add || tr[p].had)
		push_add(ls,tr[p].add,tr[p].had),
		push_add(rs,tr[p].add,tr[p].had);
	if(tr[p].tmn!=inf)
		push_min(ls,tr[p].tmn);
		push_min(rs,tr[p].tmn);
	tr[p].add=tr[p].had=0;tr[p].tmn=inf;
}

void update_sum(int p,int l,int r,int L,int R,int k){
	if(L<=l && r<=R) return push_add(p,k,max(0ll,k));
	pushdown(p);
    if(L<=mid) update_sum(ls,l,mid,L,R,k);
    if(R>mid) update_sum(rs,mid+1,r,L,R,k);
    pushup(tr[p],tr[ls],tr[rs]);
}

void update_min(int p,int l,int r,int L,int R,int v){
	if(tr[p].mx1<=v) return;
	if(L<=l && r<=R && tr[p].mx2<v) return push_min(p,v);
	pushdown(p);
	if(L<=mid) update_min(ls,l,mid,L,R,v);
	if(R>mid) update_min(rs,mid+1,r,L,R,v);
	pushup(tr[p],tr[ls],tr[rs]);
}

Tr ask(int p,int l,int r,int L,int R){
	if(L<=l && r<=R) return tr[p];pushdown(p);
	if(R<=mid) return ask(ls,l,mid,L,R);
	if(L>mid) return ask(rs,mid+1,r,L,R);
	Tr T;return pushup(T,ask(ls,l,mid,L,mid),ask(rs,mid+1,r,mid+1,R)),T;
}

signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m;For(i,1,n) a[i]=input();
	int Tim=clock();build(1,1,n);
	while(m--){
		int opt=input(),l=input(),r=input(),k;
		if(opt==1) update_sum(1,1,n,l,r,k=input());
		if(opt==2) update_min(1,1,n,l,r,k=input());
		if(opt==3) cout<<ask(1,1,n,l,r).sum<<'\n';
		if(opt==4) cout<<ask(1,1,n,l,r).mx1<<'\n';
		if(opt==5) cout<<ask(1,1,n,l,r).hmx<<'\n';
	}return cerr<<"TIME:"<<(clock()-Tim)/1000.,0;
}

100pts

#include<bits/stdc++.h>
#define For(i,l,r) for(int i=l;i<=r;i++)
#define Rof(i,l,r) for(int i=l;i>=r;i--)
using namespace std;
#define int long long
inline int input(){int x;return cin>>x,x;}
const int inf = 0x3f3f3f3f3f3f3f3f;
const int N = 501234;

int n,m,a[N];

#define mid (l+r>>1)
#define ls (p<<1)
#define rs (p<<1|1) 
struct Tr{
	int sum,siz,cmx;
	int mx1,mx2,hmx;
	int ad1,had1,ad2,had2;
}tr[N<<2|3];

void pushup(Tr &T,Tr L,Tr R){
	T.sum=L.sum+R.sum;
	T.mx1=max(L.mx1,R.mx1);
	if(L.mx1==R.mx1){
		T.mx2=max(L.mx2,R.mx2);
		T.cmx=L.cmx+R.cmx;
	}else{
		T.mx2=max(min(L.mx1,R.mx1),max(L.mx2,R.mx2));
		T.cmx=L.mx1>R.mx1?L.cmx:R.cmx;
	}T.hmx=max(L.hmx,R.hmx);
}

void build(int p,int l,int r){
	tr[p]={a[l],r-l+1,1,a[l],-inf,a[l],0,0,0,0};
	if(l==r) return;
	build(ls,l,mid),build(rs,mid+1,r);
	pushup(tr[p],tr[ls],tr[rs]);
}

void modify(int p,int k1,int hk1,int k2,int hk2){
	tr[p].sum += (tr[p].cmx * k1 + (tr[p].siz - tr[p].cmx)*k2);
	tr[p].hmx = max(tr[p].hmx, tr[p].mx1 + hk1);
	tr[p].mx1 += k1;
	
	if(tr[p].mx2!=-inf) tr[p].mx2+=k2;
	
	tr[p].had1 = max(tr[p].had1, tr[p].ad1 + hk1);
	tr[p].ad1 += k1;
	tr[p].had2 = max(tr[p].had2, tr[p].ad2 + hk2);
	tr[p].ad2 += k2;
}

void pushdown(int p){
	int maxn = max(tr[ls].mx1, tr[rs].mx1);
	if(tr[ls].mx1 == maxn)
		modify(ls,tr[p].ad1,tr[p].had1,tr[p].ad2,tr[p].had2);
	else modify(ls,tr[p].ad2,tr[p].had2,tr[p].ad2,tr[p].had2);
	
	if(tr[rs].mx1 == maxn)
		modify(rs,tr[p].ad1,tr[p].had1,tr[p].ad2,tr[p].had2);
	else modify(rs,tr[p].ad2,tr[p].had2,tr[p].ad2,tr[p].had2);
		
	tr[p].ad1 = tr[p].had1 = tr[p].ad2 = tr[p].had2 = 0;
}

void update_sum(int p,int l,int r,int L,int R,int k){
	if(L<=l && r<=R) return modify(p,k,k,k,k);
	pushdown(p);
    if(L<=mid) update_sum(ls,l,mid,L,R,k);
    if(R>mid) update_sum(rs,mid+1,r,L,R,k);
    pushup(tr[p],tr[ls],tr[rs]);
}

void update_min(int p,int l,int r,int L,int R,int v){
	if(tr[p].mx1<=v) return;
	if(L<=l && r<=R && tr[p].mx2<v)
		return modify(p,v-tr[p].mx1,v-tr[p].mx1,0,0);
	pushdown(p);
	if(L<=mid) update_min(ls,l,mid,L,R,v);
	if(R>mid) update_min(rs,mid+1,r,L,R,v);
	pushup(tr[p],tr[ls],tr[rs]);
}

Tr ask(int p,int l,int r,int L,int R){
	if(L<=l && r<=R) return tr[p];pushdown(p);
	if(R<=mid) return ask(ls,l,mid,L,R);
	if(L>mid) return ask(rs,mid+1,r,L,R);
	Tr T;return pushup(T,ask(ls,l,mid,L,mid),ask(rs,mid+1,r,mid+1,R)),T;
}

signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m;For(i,1,n) a[i]=input();
	int Tim=clock();build(1,1,n);
	while(m--){
		int opt=input(),l=input(),r=input(),k;
		if(opt==1) update_sum(1,1,n,l,r,k=input());
		if(opt==2) update_min(1,1,n,l,r,k=input());
		if(opt==3) cout<<ask(1,1,n,l,r).sum<<'\n';
		if(opt==4) cout<<ask(1,1,n,l,r).mx1<<'\n';
		if(opt==5) cout<<ask(1,1,n,l,r).hmx<<'\n';
	}return cerr<<"TIME:"<<(clock()-Tim)/1000.,0;
}

文章作者: WolfDeer
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 WolfDeer !
  目录