(笔记)线段树维护区间历史最值 最值操作 lazytag 应用
区间历史最值 最值操作
P6242 【模板】线段树 3(区间最值操作、区间历史最值)
先解决区间最值操作的问题。
考虑通过划分数域将区间最值操作转化为区间加减。以 \(i\in [l,r],A_i\leftarrow max(A_i,v)\) 的区间操作为例。我们对于线段树每个节点记录一个最大值 \(mx\) 及其出现次数 \(cnt\) 和一个次大值 \(cmx\),暴力递归直到存在区间使得 \(cmx<v<mx\),这时候只需要把 \(cnt\) 个 \(mx\) 全部变成 \(v\) 即可,这是 lazytag 容易维护的。可以证明当不存在其他修改操作(如区间加等)时,该操作的时间复杂度是 \(O(m\log n)\) 的。
如果存在修改操作,时间复杂度就是 \(O(m\log^2 n)\) 的,效率相当优秀了,但我不会证www。
再来解决区间历史最值得问题。这里我们只需要做一个扩展,对于每个 lazytag,复制一个新的历史 lazytag,每次向下传递的时候通过用原信息和历史 lazytag 更新历史最值,原信息和原 lazytag 更新原值即可,需要注意的是要先更新历史值,以免被覆盖。
在本题中,我们根据操作特性设计两个原始 lazytag,表示对 \(mx\) 的区间加和对非 \(mx\) 的区间加,再扩展两个历史 lazytag。
每次做标记相当于分别给 \(mx\) 和非 \(mx\) 都搞了一个区间加,写在一个函数中即可。
//tgs -> tagsum,tgsh -> tagsum history
//tgm -> tagmax,tgmh -> tagmax history
void mktag(int p,int v1,int v2,int v3,int v4){
int l=Ln[p],r=Rn[p];
sum[p]+=1ll*v1*cnt[p]+1ll*v2*(r-l+1-cnt[p]);
mxb[p]=max(mxb[p],mxa[p]+v3);
mxa[p]+=v1;
if(cmx[p]!=-INF)cmx[p]+=v2;
tgmh[p]=max(tgmh[p],tgm[p]+v3);
tgsh[p]=max(tgsh[p],tgs[p]+v4);
tgm[p]+=v1;tgs[p]+=v2;
}
我们发现历史最值更新上面所述的逻辑是相同的,需要注意的是本题还要更新 \(cmx\)。
然后我们就基本解决了这题,注意一下 pushup 中的信息合并逻辑即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=5e5+5,INF=2e9;
int a[N],L,R,k,n,m;
struct Sgt{
#define ls p<<1
#define rs p<<1|1
#define mid ((l+r)>>1)
int tgm[N<<2],tgs[N<<2],tgmh[N<<2],tgsh[N<<2];
int mxa[N<<2],mxb[N<<2],cnt[N<<2],cmx[N<<2];
int Ln[N<<2],Rn[N<<2];
LL sum[N<<2];
void pushup(int p){
sum[p]=sum[ls]+sum[rs];
mxa[p]=max(mxa[ls],mxa[rs]);
mxb[p]=max(mxb[ls],mxb[rs]);
if(mxa[ls]==mxa[rs])cnt[p]=cnt[ls]+cnt[rs],cmx[p]=max(cmx[ls],cmx[rs]);
else if(mxa[ls]<mxa[rs])cnt[p]=cnt[rs],cmx[p]=max(mxa[ls],cmx[rs]);
else cnt[p]=cnt[ls],cmx[p]=max(mxa[rs],cmx[ls]);
}
void build(int p,int l,int r){
tgm[p]=tgs[p]=tgmh[p]=tgsh[p]=0;
Ln[p]=l,Rn[p]=r;
if(l==r){
sum[p]=a[l];
mxa[p]=mxb[p]=a[l];
cnt[p]=1,cmx[p]=-INF;
return ;
}
build(ls,l,mid);
build(rs,mid+1,r);
pushup(p);
}
void mktag(int p,int v1,int v2,int v3,int v4){
int l=Ln[p],r=Rn[p];
sum[p]+=1ll*v1*cnt[p]+1ll*v2*(r-l+1-cnt[p]);
mxb[p]=max(mxb[p],mxa[p]+v3);
mxa[p]+=v1;
if(cmx[p]!=-INF)cmx[p]+=v2;
tgmh[p]=max(tgmh[p],tgm[p]+v3);
tgsh[p]=max(tgsh[p],tgs[p]+v4);
tgm[p]+=v1;tgs[p]+=v2;
}
void pushdown(int p){
int mxn=max(mxa[ls],mxa[rs]);
if(mxa[ls]==mxn)
mktag(ls,tgm[p],tgs[p],tgmh[p],tgsh[p]);
else mktag(ls,tgs[p],tgs[p],tgsh[p],tgsh[p]);
if(mxa[rs]==mxn)
mktag(rs,tgm[p],tgs[p],tgmh[p],tgsh[p]);
else mktag(rs,tgs[p],tgs[p],tgsh[p],tgsh[p]);
tgm[p]=tgs[p]=tgmh[p]=tgsh[p]=0;
}
void update_add(int p){
int l=Ln[p],r=Rn[p];
if(L<=l&&r<=R){
mktag(p,k,k,k,k);
return ;
}
pushdown(p);
if(L<=mid)update_add(ls);
if(R>mid)update_add(rs);
pushup(p);
}
void update_min(int p){
int l=Ln[p],r=Rn[p];
if(k>=mxa[p])return ;
if(L<=l&&r<=R&&cmx[p]<k){
int v=mxa[p]-k;
mktag(p,-v,0,-v,0);
return ;
}
pushdown(p);
if(L<=mid)update_min(ls);
if(R>mid)update_min(rs);
pushup(p);
}
LL quesum(int p){
int l=Ln[p],r=Rn[p];
if(L<=l&&r<=R)return sum[p];
LL res=0;
pushdown(p);
if(L<=mid)res+=quesum(ls);
if(R>mid)res+=quesum(rs);
return res;
}
int quemxa(int p){
int l=Ln[p],r=Rn[p];
if(L<=l&&r<=R)return mxa[p];
int res=-INF;
pushdown(p);
if(L<=mid)res=max(res,quemxa(ls));
if(R>mid)res=max(res,quemxa(rs));
return res;
}
int quemxb(int p){
int l=Ln[p],r=Rn[p];
if(L<=l&&r<=R)return mxb[p];
int res=-INF;
pushdown(p);
if(L<=mid)res=max(res,quemxb(ls));
if(R>mid)res=max(res,quemxb(rs));
return res;
}
#undef ls
#undef rs
#undef mid
}T;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
T.build(1,1,n);
while(m--){
int op;scanf("%d%d%d",&op,&L,&R);
if(op==1){
scanf("%d",&k);
T.update_add(1);
}
else if(op==2){
scanf("%d",&k);
T.update_min(1);
}
else if(op==3)
printf("%lld\n",T.quesum(1));
else if(op==4)
printf("%d\n",T.quemxa(1));
else
printf("%d\n",T.quemxb(1));
}
return 0;
}
lazytag 应用
P4314 CPU 监控
搞清楚 lazytag 的运算逻辑很重要。本题有两个操作,一个是区间覆盖,一个是区间加,然后有求最大值和历史最大值的询问。针对两种操作,我们需要知道,lazytag 实际上记录的是在线段树节点上的操作序列,只是有些操作可以合并所以不需要每次都设一个 vector(也开不下)。那么每次操作我们就要弄清楚其中的逻辑。
对于一个节点的区间覆盖,第一次覆盖后的任何一次操作都可以视为区间覆盖,因为区间加只需要将覆盖值上下位移即可。那么实际上每个节点的操作序列只有:
\(\text{区间加 + 区间覆盖}\)
按照这个逻辑,我们 pushdown 时先更新区间加,再更新区间覆盖即可。在区间加时,如果存在之前的区间覆盖,那么直接放在覆盖标记上。
关于 #11 Hack:由于题目数据,我们需要给区间覆盖一个单独的 bool tag 以判断其是否为空 \(\texttt{tag}\)。判断是否要更新区间加 \(\texttt{tag}\) 时需要有 \(\texttt{tagsum}\) 和 \(\texttt{tagsum hisotry}\) 任一 \(\texttt{tag}\) 非 \(0\) 就更新,理由是经过若干次加操作,\(\texttt{tagsum}\) 最后可能为 \(0\),但是过程中可能大于 \(0\),所以 \(\texttt{tagsum history}\) 大于 \(0\)。\(\texttt{tagsum history}\) 有可能为 0,但是可能是因为 \(\texttt{tagsum}\) 在更新过程中一直小于 \(0\)。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
const int INF=-(1ll<<31);
int L,R,k,a[N],n;
struct Sgt{
#define ls p<<1
#define rs p<<1|1
#define mid ((l+r)>>1)
int mx[N<<2],mxh[N<<2];
int tg[N<<2],tgh[N<<2];
bool psb[N<<2];
int tgs[N<<2],tgsh[N<<2];
int Ln[N<<2],Rn[N<<2];
void pushup(int p){
mx[p]=max(mx[ls],mx[rs]);
mxh[p]=max(mxh[ls],mxh[rs]);
}
void build(int p,int l,int r){
tgh[p]=tg[p]=INF;
Ln[p]=l,Rn[p]=r;
if(l==r){
mx[p]=mxh[p]=a[l];
return ;
}
build(ls,l,mid);
build(rs,mid+1,r);
pushup(p);
}
void mktag2(int p,int v1,int v2){
mx[p]=v1;
mxh[p]=max(mxh[p],v2);
psb[p]=1;tg[p]=v1;
tgh[p]=max(tgh[p],v2);
}
void mktag1(int p,int v1,int v2){
if(psb[p])mktag2(p,mx[p]+v1,mx[p]+v2);
else {
mxh[p]=max(mxh[p],mx[p]+v2);
mx[p]+=v1;
tgsh[p]=max(tgsh[p],tgs[p]+v2);
tgs[p]+=v1;
}
}
void pushdown(int p){
if(tgs[p]||tgsh[p]){
mktag1(ls,tgs[p],tgsh[p]);
mktag1(rs,tgs[p],tgsh[p]);
tgs[p]=tgsh[p]=0;
}
if(psb[p]){
mktag2(ls,tg[p],tgh[p]);
mktag2(rs,tg[p],tgh[p]);
psb[p]=0;
tgh[p]=tg[p]=INF;
}
}
void updadd(int p){
int l=Ln[p],r=Rn[p];
if(L<=l&&r<=R){
mktag1(p,k,k);
return ;
}
pushdown(p);
if(L<=mid)updadd(ls);
if(R>mid)updadd(rs);
pushup(p);
}
void updfg(int p){
int l=Ln[p],r=Rn[p];
if(L<=l&&r<=R){
mktag2(p,k,k);
return ;
}
pushdown(p);
if(L<=mid)updfg(ls);
if(R>mid)updfg(rs);
pushup(p);
}
int qmx(int p){
int l=Ln[p],r=Rn[p];
if(L<=l&&r<=R)return mx[p];
pushdown(p);
if(L<=mid&&R>mid)return max(qmx(ls),qmx(rs));
if(L<=mid)return qmx(ls);
return qmx(rs);
}
int qmxh(int p){
int l=Ln[p],r=Rn[p];
if(L<=l&&r<=R)return mxh[p];
pushdown(p);
if(L<=mid&&R>mid)return max(qmxh(ls),qmxh(rs));
if(L<=mid)return qmxh(ls);
return qmxh(rs);
}
#undef ls
#undef rs
#undef mid
}T;
char c;
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
T.build(1,1,n);
int E;cin>>E;
while(E--){
cin>>c>>L>>R;
if(c=='Q')cout<<T.qmx(1)<<'\n';
else if(c=='A')cout<<T.qmxh(1)<<'\n';
else if(c=='P'){
cin>>k;
T.updadd(1);
}
else {
cin>>k;
T.updfg(1);
}
}
return 0;
}

浙公网安备 33010602011771号