线段树综合
线段树综合
自从模拟赛出现一道勾石线段树题目开始,命运的齿轮就开始转动。
线段树分裂
之前学过线段树合并,现在又要学线段树分裂了 \(\text{qwq}\)。
线段树分裂意在将线段树维护的信息拆成多个区间进行维护。不难发现,如果直接重新建树是 \(O(n\log n)\) 的,而线段树分裂能够做到 \(O(k\log n)\),\(k\) 是拆分成的区间个数。
在这个基础下,线段树分裂又分为大小拆分和下标拆分。顾名思义,大小拆分是通过给定原线段树在拆分后剩余的大小进行拆分的;而下标拆分则是通过原线段树在拆分后维护的信息所在区间的范围进行拆分的。
代码
void Split_Size(int u,int &v,int k){
if(!u)return ;
v=NewNode();//建立新结点
int a=siz[ls[u]];
if(a<k)Split_Size(rs[u],rs[v],k-a);//如果左区间元素的个数不够要求,那么左区间一定全部在原线段树中,对右区间进行处理即可
else swap(rs[u],rs[v]);//不然右区间一定全部在分裂出的线段树中,将原线段树维护的内容给到新线段树即可
if(k<a)Split_Size(ls[u],ls[v],k);//如果左区间不全是原线段树的,那么需要对左区间进行处理
siz[v]=siz[u]-k;//更新大小
siz[u]=k;
return ;
}//通过大小分裂线段树
void Split_Index(int u,int &v,int l,int r,int p){
if(!u)return ;
v=NewNode();//建立新结点
int mid=(l+r)>>1;
if(mid<p)Split(rs[u],rs[v],mid+1,r,p);//如果原线段树包含整个左区间,那么只需要对右区间进行处理
else swap(rs[u],rs[v]);//不然右区间一定全部在分裂出的线段树中,将原线段树维护的内容给到新线段树即可
if(p<mid)Split(ls[u],ls[v],l,mid,p);//如果左区间不全是原线段树的,那么需要对左区间进行处理
PushUp(u,ls[u],rs[u]);//更新大小
PushUp(v,ls[v],rs[v]);
return ;
}//通过下标分裂线段树
时间复杂度
不难发现,在每一层递归中,都只会调用一层函数,所以复杂度是线段树的层高,也就是 \(O(\log n)\)。
值得注意的是,我的两种写法对于分裂出空线段树会存在空间浪费,也就是空结点的情况,可能会对时空复杂度造成影响,因此对于可能分裂出空线段树的操作最好是特判。
线段树优化建图
线段树优化建图常见于题目中出现多对一或一对多的对应关系时优化正常做法。因为一对多的关系,不仅单独建边会导致空间复杂度过大,而且时间复杂度也无法保证。此时就需要利用线段树能够将一个区间分成最多 \(\log n\) 段,此时,建边的时空复杂度均为 \(O(q\log n)\),\(q\) 为建边关系的个数。接着通过线段树原有的边将关系下放就可以得到正确答案。接着,建图又分为点为起点、区间为终点和区间为起点、点为终点的建边关系。
代码(点 \(\to\) 区间)
void Build(int id,int l,int r){
if(l==r){
idx[l]=id;//记录每一个位置对应在线段树上的位置
return ;
}
int mid=(l+r)>>1;
Build(lid,l,mid);
Build(rid,mid+1,r);
AddEdge(id,lid);//线段树上将关系下放的边
AddEdge(id,rid);
return ;
}
void Insert(int id,int l,int r,int p,int q,int v){
if(p<=l&&r<=q){
AddEdge(v,id);//将对应点和对应区间连接
return ;
}
int mid=(l+r)>>1;
if(p<=mid)Insert(lid,l,mid,p,q,v);
if(q>mid)Insert(rid,mid+1,r,p,q,v);
return ;
}
需要注意的是,上文的代码只适用于以点为起点,区间为终点的建边关系。对于以区间为起点,点为终点的建边关系,需要将线段树上的边反向,进行上抬才可以。
代码(区间 \(\to\) 点)
void Build(int id,int l,int r){
if(l==r){
AddEdge(l,id+n);
idx[l]=id;//统计每一个点的对应位置
return ;
}
int mid=(l+r)>>1;
Build(lid,l,mid);
Build(rid,mid+1,r);
AddEdge(lid+n,id+n);//建立上抬关系的边
AddEdge(rid+n,id+n);
return ;
}
void Insert(int id,int l,int r,int p,int q,int v){
if(p<=l&&r<=q){
AddEdge(id,v);//从区间到对应点连边
return ;
}
int mid=(l+r)>>1;
if(p<=mid)Insert(lid,l,mid,p,q,v);
if(q>mid)Insert(rid,mid+1,r,p,q,v);
return ;
}
而对于两种关系同时存在的图,我们需要建两颗线段树,一棵线段树用于下放关系,一棵树用来上抬关系,保证在区间内的单点,能够上升到对应区间中,也就是以第二棵线段树为关系发出的点,第一棵线段树为关系接受的点。同时,两棵线段树之间的单点也是对应的,需要连边保证连通。
代码(两者都有)
void Build(int id,int l,int r){
if(l==r){
idx[l]=id;
S=max(S,id);//确定第二棵线段树编号的偏移量
return ;
}
int mid=(l+r)>>1;
Build(lid,l,mid);
Build(rid,mid+1,r);
return ;
}
void Connect(int id,int l,int r){
if(l==r){
AddEdge(id,id+S);
return ;
}
int mid=(l+r)>>1;
AddEdge(id,lid);//一棵树下放
AddEdge(id,rid);
AddEdge(lid+S,id+S);//一棵树上抬
AddEdge(rid+S,id+S);
Connect(lid,l,mid);
Connect(rid,mid+1,r);
return ;
}
void Insert(int id,int l,int r,int p,int q,int v,int opt){
if(p<=l&&r<=q){
if(opt==0)AddEdge(v+S,id);
else AddEdge(id+S,v);//分情况连边
return ;
}
int mid=(l+r)>>1;
if(p<=mid)Insert(lid,l,mid,p,q,v,opt);
if(q>mid)Insert(rid,mid+1,r,p,q,v,opt);
return ;
}
最后在优化后的图上进行相同的操作即可。
线段树分治
线段树分治一般用于操作有时限性的题目,在这种题目中,可以用线段树分治,每一个节点代表时间的范围,在一个结点的子树内部时就代表该结点对应的操作在当前节点存在,反之则代表不存在。不难发现我们只需要遍历这一颗线段树就可以得到每一个时刻或时间段的结果,而线段树具有良好的 \(O(n)\) 个结点,复杂度十分优秀。接着每一个操作对应的时间段都被拆成 \(O(\log n)\) 个的时间段,因此操作的更新的复杂度是 \(O(q\log n)\) 的。
代码
void Dfs(int id,int l,int r){
int siz=(int)s.size();
for(int i=0;i<(int)T[id].size();i++){//记录结点对应的操作信息
/*
统计该节点的操作对答案的影响
*/
}
if(l==r){
/*
输出统计到的答案
*/
}else{
int mid=(l+r)>>1;
Dfs(lid,l,mid);
Dfs(rid,mid+1,r);//递归继续求解
}
/*
撤销影响
*/
return ;
}
线段树二分
顾名思义,线段树二分就是在线段树上进行二分。利用线段树维护的单调的信息来求解触合理的区间范围进行一系列操作的时候就需要用到线段树二分。但实际上代码非常简单,因为线段树本身就具有折半分割的特点,所以只需要判断左子树是否符合条件,若不符合则递归右子树;反之递归左子树。
线段树二分一般适用于某一个端点是已知的,需要求解另一个端点的情况。
代码
int Find(int id,int l,int r){
if(l==r)return l;
int mid=(l+r)>>1;
//查询左子树信息,并判断是否符合条件
if(true)return Find(lid,l,mid,hgt);//调用左子树继续查找
else return Find(rid,mid+1,r,hgt);//调用右子树继续查找
}
树状数组套权值线段树
众所周知,主席树是静态的,因为一但涉及修改,那么主席树就需要将大量线段树重构,因此复杂度不优。我们不难联想到,主席树的朴素写法类似于前缀和,可以求解区间和问题,而涉及修改时,则可以采用树状数组,因此,我们不妨按照同样的思路,利用树状数组维护主席树所维护的前缀线段树。
那么修改的思路就是将所有树状数组对应的位置修改即可,复杂度是 \(O(\log^2 n)\) 的。查询的时候则是将所有需要查询后累加的位置提前存储,在查询的过程中将所有的位置均向下跳即可,复杂度也是 \(O(\log^2 n)\) 的。
代码
for(int i=x;i<=n;i+=lowbit(i))Insert(r[i],r[i],1,sum,a[x],1);//修改
-------------------------------------------------------------------
int Query(int l,int r){
if(l==r)//返回结果
//统计右端点对应区间和左端点对应区间
int mid=(l+r)>>1;
if(true){//如果左区间满足条件
for(int i=1;i<=cnt1;i++)L[i]=t[L[i]].ls;//全部向左下移
for(int i=1;i<=cnt2;i++)R[i]=t[R[i]].ls;
return Query(l,mid,k);
}else{
for(int i=1;i<=cnt1;i++)L[i]=t[L[i]].rs;//否则全部向右下移
for(int i=1;i<=cnt2;i++)R[i]=t[R[i]].rs;
return Query(mid+1,r,k-cur);
}
}
for(int i=x-1;i>0;i-=lowbit(i))L[++cnt1]=r[i];
for(int i=y;i>0;i-=lowbit(i))R[++cnt2]=r[i];
printf("%d",Query(1,n));//查询
标记拼接
其实很简单,只是对于一些题目,我们不好直接维护需要的信息,那么我们就可以通过一些零碎的标记来共同维护出需要的信息。如山海经中的区间最大子段和,就是通过区间和、靠左的最大子段和、靠右的最大子段和维护出区间最大子段和的。
吉司机线段树
吉司机线段树常用于维护区间最值和区间历史最值操作。
区间最值
区间最值操作, 即对整个区间取 \(\min\),形式化的,就是形如 \(\forall i\in[l,r],a_i\leftarrow \min(a_i,x)\) 的操作。这其中又分为带区间加减和不带区间加减的操作。
不带区间加减
给一个长度为 \(n\) 的序列,有 \(m\) 次操作:区间取 \(\min\),区间求 \(\max\),区间求和。
考虑到区间取 \(\min\) 操作本质上相当于将所有 \(>x\) 的值赋为 \(x\)。所以我们有一个简单的思路:对于线段树上的每一个点,我们维护对应区间的最大值 \(\text{mx}\),最大值个数 \(\text{cnt}\),严格次大值 \(\text{nd}\),以及区间和 \(\text{sum}\)。接下来对于当前节点
- 如果 \(\text{mx}\le x\),则不必对此区间进行操作。
- 如果 \(\text{nd}<x<\text{mx}\),说明这个区间中只有最大值 \(\text{mx}\) 会被修改成 \(x\),那么此时区间和显然增加了 \(\text{cnt}\times(x-\text{mx})\),最后将整个区间打上标记,表示当前区间的最大值修改为 \(x\)。
- 如果 \(x\le \text{nd}\),则我们难以直接维护,不妨考虑直接暴力递归,直到满足上面两个条件之一。
通过势能分析法我们得出复杂度是 \(O(m\log n)\) 的。
code(HDU5306)
#include <bits/stdc++.h>
using namespace std;
#define il inline
#define ll long long
const int N=1e6+5;
int T,n,m;
int a[N];
namespace SGT{
#define lid (id<<1)
#define rid ((id<<1)|1)
int mx_st[N<<2],mx_nd[N<<2],cnt[N<<2],tag[N<<2];
ll sum[N<<2];
il void PushUp(int id){
if(mx_st[lid]==mx_st[rid]){
mx_st[id]=mx_st[lid],cnt[id]=cnt[lid]+cnt[rid];
mx_nd[id]=max(mx_nd[lid],mx_nd[rid]);
}else if(mx_st[lid]>mx_st[rid]){
mx_st[id]=mx_st[lid],cnt[id]=cnt[lid];
mx_nd[id]=max(mx_nd[lid],mx_st[rid]);
}else{
mx_st[id]=mx_st[rid],cnt[id]=cnt[rid];
mx_nd[id]=max(mx_st[lid],mx_nd[rid]);
}
sum[id]=sum[lid]+sum[rid];
return ;
}
il void Build(int id,int l,int r){
tag[id]=-1;
if(l==r){
mx_st[id]=sum[id]=a[l],cnt[id]=1,mx_nd[id]=-1;
return ;
}
int mid=(l+r)>>1;
Build(lid,l,mid),Build(rid,mid+1,r);
PushUp(id);
return ;
}
il void PushTag(int id,int v){
if(v>=mx_st[id])return ;
sum[id]+=(ll)cnt[id]*(v-mx_st[id]);
mx_st[id]=tag[id]=v;
return ;
}
il void PushDown(int id){
if(tag[id]^-1){
PushTag(lid,tag[id]),PushTag(rid,tag[id]);
tag[id]=-1;
}
return ;
}
il void Min(int id,int l,int r,int p,int q,int v){
if(v>=mx_st[id])return ;
if(p<=l&&r<=q&&v>mx_nd[id]){
PushTag(id,v);
return ;
}
PushDown(id);
int mid=(l+r)>>1;
if(p<=mid)Min(lid,l,mid,p,q,v);
if(q>mid)Min(rid,mid+1,r,p,q,v);
PushUp(id);
return ;
}
il int Max(int id,int l,int r,int p,int q){
if(p<=l&&r<=q)return mx_st[id];
PushDown(id);
int mid=(l+r)>>1;
if(q<=mid)return Max(lid,l,mid,p,q);
else if(p>mid)return Max(rid,mid+1,r,p,q);
else return max(Max(lid,l,mid,p,q),Max(rid,mid+1,r,p,q));
}
il ll Sum(int id,int l,int r,int p,int q){
if(p<=l&&r<=q)return sum[id];
PushDown(id);
int mid=(l+r)>>1;
if(q<=mid)return Sum(lid,l,mid,p,q);
else if(p>mid)return Sum(rid,mid+1,r,p,q);
else return Sum(lid,l,mid,p,q)+Sum(rid,mid+1,r,p,q);
}
}
il void Build(){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
SGT::Build(1,1,n);
for(int i=1;i<=m;i++){
int o,l,r,t;
cin>>o>>l>>r;
switch(o){
case 0:{
cin>>t;
SGT::Min(1,1,n,l,r,t);
break;
}case 1:{
cout<<SGT::Max(1,1,n,l,r)<<"\n";
break;
}default:{
cout<<SGT::Sum(1,1,n,l,r)<<"\n";
break;
}
}
}
return ;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>T;
while(T--)Build();
return 0;
}
带区间加减
考虑因为区间取 \(\min\) 的缘故,从而导致我们的最大值与其他值的加法标记会变得不一样,那我们不妨直接考虑分开维护最大值的标记和非最大值的标记,这样在暴力修改时的赋值也可以视作加减,直接更改最大值的标记即可。而在区间加的时候同时维护两个标记即可。
有一个长为 \(n\) 的序列,有 \(m\) 次操作:区间加,区间取 \(\min\),区间取 \(\max\),区间和,区间 \(\min\),区间 \(\max\)。
有区间最值操作自然想到吉司机线段树,考虑维护最大值,严格次大值,最大值个数,最小值,严格次小值,最小值个数,区间和,最大值加法标记,最小值加法标记和其他值加法标记。于是我们可以对于任何操作用这些标记实现。
有几个需要注意的点:
- 在下方标记的时候,我们需要判断左右两个子树是否含有当前区间的最小值和最大值,如果没有的话,则对于子树的最大值,应当用其他值的标记下放。
- 如果一个区间的值域较小,那么此时可能会出现一个值既是最大值又是最小值,需要我们特判一下,从而将正确的标记下放。同样严格次大值可能是最小值,严格次小值可能是最大值。
此时的复杂度是 \(O(m\log^2 n)\)。
code(BZOJ4695)
#include <bits/stdc++.h>
using namespace std;
#define il inline
#define ll long long
const int N=5e5+5,inf=1e9;
int n,m;
int a[N];
namespace SGT{
#define lid (id<<1)
#define rid ((id<<1)|1)
int mx_st[N<<2],mx_nd[N<<2],mn_st[N<<2],mn_nd[N<<2];
int cnt_mx[N<<2],cnt_mn[N<<2];
int add_mx[N<<2],add_mn[N<<2],add_es[N<<2];
ll sum[N<<2];
il void PushUp(int id){
if(mx_st[lid]==mx_st[rid]){
mx_st[id]=mx_st[lid],cnt_mx[id]=cnt_mx[lid]+cnt_mx[rid];
mx_nd[id]=max(mx_nd[lid],mx_nd[rid]);
}else if(mx_st[lid]>mx_st[rid]){
mx_st[id]=mx_st[lid],cnt_mx[id]=cnt_mx[lid];
mx_nd[id]=max(mx_nd[lid],mx_st[rid]);
}else{
mx_st[id]=mx_st[rid],cnt_mx[id]=cnt_mx[rid];
mx_nd[id]=max(mx_st[lid],mx_nd[rid]);
}
if(mn_st[lid]==mn_st[rid]){
mn_st[id]=mn_st[lid],cnt_mn[id]=cnt_mn[lid]+cnt_mn[rid];
mn_nd[id]=min(mn_nd[lid],mn_nd[rid]);
}else if(mn_st[lid]<mn_st[rid]){
mn_st[id]=mn_st[lid],cnt_mn[id]=cnt_mn[lid];
mn_nd[id]=min(mn_nd[lid],mn_st[rid]);
}else{
mn_st[id]=mn_st[rid],cnt_mn[id]=cnt_mn[rid];
mn_nd[id]=min(mn_st[lid],mn_nd[rid]);
}
sum[id]=sum[lid]+sum[rid];
return ;
}
il void Build(int id,int l,int r){
add_mx[id]=add_mn[id]=add_es[id]=0;
if(l==r){
mx_st[id]=mn_st[id]=sum[id]=a[l],mx_nd[id]=-inf,mn_nd[id]=inf;
cnt_mx[id]=cnt_mn[id]=1;
return ;
}
int mid=(l+r)>>1;
Build(lid,l,mid),Build(rid,mid+1,r);
PushUp(id);
return ;
}
il void PushTag(int id,int l,int r,int amx,int amn,int aes){
if(mx_st[id]==mn_st[id]){
if(amx==aes)amx=amn;
else amn=amx;
sum[id]+=(ll)cnt_mx[id]*amx;
}else{
sum[id]+=(ll)cnt_mx[id]*amx+(ll)cnt_mn[id]*amn+(ll)(r-l+1-cnt_mx[id]-cnt_mn[id])*aes;
if(mx_nd[id]==mn_st[id])mx_nd[id]+=amn;
else mx_nd[id]+=aes;
if(mn_nd[id]==mx_st[id])mn_nd[id]+=amx;
else mn_nd[id]+=aes;
}
mx_st[id]+=amx,mn_st[id]+=amn;
add_mx[id]+=amx,add_mn[id]+=amn,add_es[id]+=aes;
return ;
}
il void PushDown(int id,int l,int r){
int mid=(l+r)>>1,mx=max(mx_st[lid],mx_st[rid]),mn=min(mn_st[lid],mn_st[rid]);
PushTag(lid,l,mid,mx_st[lid]==mx?add_mx[id]:add_es[id]
,mn_st[lid]==mn?add_mn[id]:add_es[id],add_es[id]);
PushTag(rid,mid+1,r,mx_st[rid]==mx?add_mx[id]:add_es[id]
,mn_st[rid]==mn?add_mn[id]:add_es[id],add_es[id]);
add_mx[id]=add_mn[id]=add_es[id]=0;
return ;
}
il void Add(int id,int l,int r,int p,int q,int v){
if(p<=l&&r<=q){
PushTag(id,l,r,v,v,v);
return ;
}
PushDown(id,l,r);
int mid=(l+r)>>1;
if(p<=mid)Add(lid,l,mid,p,q,v);
if(q>mid)Add(rid,mid+1,r,p,q,v);
PushUp(id);
return ;
}
il void Max(int id,int l,int r,int p,int q,int v){
if(v<=mn_st[id])return ;
if(p<=l&&r<=q&&v<mn_nd[id]){
PushTag(id,l,r,0,v-mn_st[id],0);
return ;
}
PushDown(id,l,r);
int mid=(l+r)>>1;
if(p<=mid)Max(lid,l,mid,p,q,v);
if(q>mid)Max(rid,mid+1,r,p,q,v);
PushUp(id);
return ;
}
il void Min(int id,int l,int r,int p,int q,int v){
if(v>=mx_st[id])return ;
if(p<=l&&r<=q&&v>mx_nd[id]){
PushTag(id,l,r,v-mx_st[id],0,0);
return ;
}
PushDown(id,l,r);
int mid=(l+r)>>1;
if(p<=mid)Min(lid,l,mid,p,q,v);
if(q>mid)Min(rid,mid+1,r,p,q,v);
PushUp(id);
return ;
}
il ll Sum(int id,int l,int r,int p,int q){
if(p<=l&&r<=q)return sum[id];
PushDown(id,l,r);
int mid=(l+r)>>1;
if(q<=mid)return Sum(lid,l,mid,p,q);
else if(p>mid)return Sum(rid,mid+1,r,p,q);
else return Sum(lid,l,mid,p,q)+Sum(rid,mid+1,r,p,q);
}
il int Max(int id,int l,int r,int p,int q){
if(p<=l&&r<=q)return mx_st[id];
PushDown(id,l,r);
int mid=(l+r)>>1;
if(q<=mid)return Max(lid,l,mid,p,q);
else if(p>mid)return Max(rid,mid+1,r,p,q);
else return max(Max(lid,l,mid,p,q),Max(rid,mid+1,r,p,q));
}
il int Min(int id,int l,int r,int p,int q){
if(p<=l&&r<=q)return mn_st[id];
PushDown(id,l,r);
int mid=(l+r)>>1;
if(q<=mid)return Min(lid,l,mid,p,q);
else if(p>mid)return Min(rid,mid+1,r,p,q);
else return min(Min(lid,l,mid,p,q),Min(rid,mid+1,r,p,q));
}
}
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];
SGT::Build(1,1,n);
cin>>m;
while(m--){
int o,l,r,x;
cin>>o>>l>>r;
switch(o){
case 1:{
cin>>x;
SGT::Add(1,1,n,l,r,x);
break;
}case 2:{
cin>>x;
SGT::Max(1,1,n,l,r,x);
break;
}case 3:{
cin>>x;
SGT::Min(1,1,n,l,r,x);
break;
}case 4:{
cout<<SGT::Sum(1,1,n,l,r)<<"\n";
break;
}case 5:{
cout<<SGT::Max(1,1,n,l,r)<<"\n";
break;
}default:{
cout<<SGT::Min(1,1,n,l,r)<<"\n";
break;
}
}
}
return 0;
}
区间历史最值
在这种题型下,我们一般需要多维护一个序列 \(b\),在每一次操作完成后,\(b\) 都会和 \(a\) 进行取 \(\min\),最后会询问有关两个序列的信息。我们先考虑一个较为简单的情况。
不带区间最值
有一个长为 \(n\) 的序列,有 \(m\) 次操作:查询区间 \(\max\),查询区间历史 \(\max\),区间加,区间赋值。
我们按照题意,对每一个区间维护当前区间的最值和历史最值,我们记作 \(\begin{bmatrix}a&b\end{bmatrix}\),此时我们可以通过引入一个东西来完成它的维护——广义矩阵乘法。
对于 \(C_{i,j}=\sum A_{i,k}B_{k,j}\) 形式的矩阵乘法,我们不妨将其看成一种二元运算组 \((\times,+)\) 对应的乘法。假如我们将运算组改成 \((+,\max)\),则形式会变为 \(C_{i,j}=\max(A_{i,k}+B_{k,j})\),而这种矩阵乘法依然满足结合律。
对于区间加,显然我们想通过一个转移矩阵 \(T\) 使得 \(\begin{bmatrix}a&b\end{bmatrix}T=\begin{bmatrix}a+k&\max(b,a+k)\end{bmatrix}\),不难构造出
但是对于区间覆盖来说,仅靠两维数组貌似有一些困难,但我们可以加上一维辅助变量,从而有
而上面的区间加也应当相应改成
然而即使我们构造除了这个矩阵,我们也不能直接暴力去乘从而获得一个 \(3^3\) 的常数,不难发现两个转移矩阵的不同之处仅在于 \(4\) 个位置,于是我们可以将一个转移矩阵看成 \(\begin{bmatrix}a&b&-\infty\\-\infty&0&-\infty\\c&d&0\end{bmatrix}\) 的形式,并且考虑到
从而我们发现矩阵仅仅在 \(a,b,c,d\) 四个位置有不同,直接维护即可。注意到在 \((+,\max)\) 意义下矩阵的单位元是 \(\begin{bmatrix}0&-\infty&-\infty\\-\infty&0&-\infty\\-\infty&-\infty&0\end{bmatrix}\),因此注意赋初始值的时候要将标记赋成这个样子。
code(P4314)
#include <bits/stdc++.h>
using namespace std;
#define il inline
#define ll long long
const int N=1e5+5;
const ll INF=1e18;
int n,m;
int a[N];
struct Tag{
ll a,b,c,d;
friend Tag operator +(Tag f,Tag g){return {max(-INF,f.a+g.a),max(f.a+g.b,f.b)
,max(f.c+g.a,g.c),max({f.c+g.b,f.d,g.d})};}
};
namespace SGT{
#define lid (id<<1)
#define rid ((id<<1)|1)
ll mx[N<<2],hmx[N<<2];
Tag tag[N<<2];
il void PushUp(int id){
mx[id]=max(mx[lid],mx[rid]),hmx[id]=max(hmx[lid],hmx[rid]);
return ;
}
il void Build(int id,int l,int r){
tag[id]={0,-INF,-INF,-INF};
if(l==r){
mx[id]=hmx[id]=a[l];
return ;
}
int mid=(l+r)>>1;
Build(lid,l,mid),Build(rid,mid+1,r);
PushUp(id);
return ;
}
il void PushTag(int id,Tag tg){
hmx[id]=max({mx[id]+tg.b,hmx[id],tg.d}),mx[id]=max(mx[id]+tg.a,tg.c);
tag[id]=tag[id]+tg;
return ;
}
il void PushDown(int id){
PushTag(lid,tag[id]),PushTag(rid,tag[id]);
tag[id]={0,-INF,-INF,-INF};
return ;
}
il void Add(int id,int l,int r,int p,int q,int v){
if(p<=l&&r<=q){
PushTag(id,{v,v,-INF,-INF});
return ;
}
PushDown(id);
int mid=(l+r)>>1;
if(p<=mid)Add(lid,l,mid,p,q,v);
if(q>mid)Add(rid,mid+1,r,p,q,v);
PushUp(id);
return ;
}
il void Assign(int id,int l,int r,int p,int q,int v){
if(p<=l&&r<=q){
PushTag(id,{-INF,-INF,v,v});
return ;
}
PushDown(id);
int mid=(l+r)>>1;
if(p<=mid)Assign(lid,l,mid,p,q,v);
if(q>mid)Assign(rid,mid+1,r,p,q,v);
PushUp(id);
return ;
}
il int Mx(int id,int l,int r,int p,int q){
if(p<=l&&r<=q)return mx[id];
PushDown(id);
int mid=(l+r)>>1;
if(q<=mid)return Mx(lid,l,mid,p,q);
else if(p>mid)return Mx(rid,mid+1,r,p,q);
else return max(Mx(lid,l,mid,p,q),Mx(rid,mid+1,r,p,q));
}
il int Hmx(int id,int l,int r,int p,int q){
if(p<=l&&r<=q)return hmx[id];
PushDown(id);
int mid=(l+r)>>1;
if(q<=mid)return Hmx(lid,l,mid,p,q);
else if(p>mid)return Hmx(rid,mid+1,r,p,q);
else return max(Hmx(lid,l,mid,p,q),Hmx(rid,mid+1,r,p,q));
}
}
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];
SGT::Build(1,1,n);
cin>>m;
while(m--){
char o;int l,r,x;
cin>>o>>l>>r;
switch(o){
case 'Q':{
cout<<SGT::Mx(1,1,n,l,r)<<"\n";
break;
}case 'A':{
cout<<SGT::Hmx(1,1,n,l,r)<<"\n";
break;
}case 'P':{
cin>>x;
SGT::Add(1,1,n,l,r,x);
break;
}default:{
cin>>x;
SGT::Assign(1,1,n,l,r,x);
break;
}
}
}
return 0;
}
带区间最值
给定一个长为 \(n\) 的序列,有 \(m\) 此操作:区间取 \(\min\),区间加,区间求和,区间 \(\max\),区间历史 \(\max\)。
依旧考虑将最大值和其他值的标记分开维护,然后此题就没有什么要点了。注意区间非最大值的历史最值有可能是当前区间的历史最值,因此在查询和更新的时候都必须包含非最大值的历史最值。
code(P6242)
#include <bits/stdc++.h>
using namespace std;
#define il inline
#define ll long long
const int N=5e5+5;
const ll INF=1e15;
int n,m;
int a[N];
struct Tag{
ll a,b;
friend Tag operator +(Tag f,Tag g){return {max(f.a,f.b+g.a),f.b+g.b};}
};
namespace SGT{
#define lid (id<<1)
#define rid ((id<<1)|1)
int cnt[N<<2];
ll mx_st[N<<2],mx_nd[N<<2],hmx_st[N<<2],hmx_es[N<<2],sum[N<<2];
Tag tag_st[N<<2],tag_es[N<<2];
il void PushUp(int id){
if(mx_st[lid]==mx_st[rid]){
mx_st[id]=mx_st[lid],cnt[id]=cnt[lid]+cnt[rid],hmx_st[id]=max(hmx_st[lid],hmx_st[rid]);
mx_nd[id]=max(mx_nd[lid],mx_nd[rid]),hmx_es[id]=max(hmx_es[lid],hmx_es[rid]);
}else if(mx_st[lid]>mx_st[rid]){
mx_st[id]=mx_st[lid],cnt[id]=cnt[lid],hmx_st[id]=hmx_st[lid];
mx_nd[id]=max(mx_nd[lid],mx_st[rid]),hmx_es[id]=max({hmx_es[lid],hmx_st[rid],hmx_es[rid]});
}else{
mx_st[id]=mx_st[rid],cnt[id]=cnt[rid],hmx_st[id]=hmx_st[rid];
mx_nd[id]=max(mx_st[lid],mx_nd[rid]),hmx_es[id]=max({hmx_st[lid],hmx_es[lid],hmx_es[rid]});
}
sum[id]=sum[lid]+sum[rid];
return ;
}
il void Build(int id,int l,int r){
tag_st[id]=tag_es[id]={-INF,0};
if(l==r){
mx_st[id]=hmx_st[id]=sum[id]=a[l],mx_nd[id]=hmx_es[id]=-INF,cnt[id]=1;
return ;
}
int mid=(l+r)>>1;
Build(lid,l,mid),Build(rid,mid+1,r);
PushUp(id);
return ;
}
il void PushTag(int id,int l,int r,Tag tst,Tag tes){
sum[id]+=cnt[id]*tst.b+(r-l+1-cnt[id])*tes.b;
hmx_st[id]=max(hmx_st[id],mx_st[id]+tst.a),mx_st[id]+=tst.b;
if(mx_nd[id]^-INF)hmx_es[id]=max(hmx_es[id],mx_nd[id]+tes.a),mx_nd[id]+=tes.b;
tag_st[id]=tag_st[id]+tst,tag_es[id]=tag_es[id]+tes;
return ;
}
il void PushDown(int id,int l,int r){
int mid=(l+r)>>1,mx=max(mx_st[lid],mx_st[rid]);
PushTag(lid,l,mid,mx_st[lid]==mx?tag_st[id]:tag_es[id],tag_es[id]);
PushTag(rid,mid+1,r,mx_st[rid]==mx?tag_st[id]:tag_es[id],tag_es[id]);
tag_st[id]={-INF,0},tag_es[id]={-INF,0};
return ;
}
il void Add(int id,int l,int r,int p,int q,int v){
if(p<=l&&r<=q){
PushTag(id,l,r,{v,v},{v,v});
return ;
}
PushDown(id,l,r);
int mid=(l+r)>>1;
if(p<=mid)Add(lid,l,mid,p,q,v);
if(q>mid)Add(rid,mid+1,r,p,q,v);
PushUp(id);
return ;
}
il void Min(int id,int l,int r,int p,int q,int v){
if(v>=mx_st[id])return ;
if(p<=l&&r<=q&&v>mx_nd[id]){
PushTag(id,l,r,{v-mx_st[id],v-mx_st[id]},{0,0});
return ;
}
PushDown(id,l,r);
int mid=(l+r)>>1;
if(p<=mid)Min(lid,l,mid,p,q,v);
if(q>mid)Min(rid,mid+1,r,p,q,v);
PushUp(id);
return ;
}
il ll Sum(int id,int l,int r,int p,int q){
if(p<=l&&r<=q)return sum[id];
PushDown(id,l,r);
int mid=(l+r)>>1;
if(q<=mid)return Sum(lid,l,mid,p,q);
else if(p>mid)return Sum(rid,mid+1,r,p,q);
else return Sum(lid,l,mid,p,q)+Sum(rid,mid+1,r,p,q);
}
il int Mx(int id,int l,int r,int p,int q){
if(p<=l&&r<=q)return mx_st[id];
PushDown(id,l,r);
int mid=(l+r)>>1;
if(q<=mid)return Mx(lid,l,mid,p,q);
else if(p>mid)return Mx(rid,mid+1,r,p,q);
else return max(Mx(lid,l,mid,p,q),Mx(rid,mid+1,r,p,q));
}
il int Hmx(int id,int l,int r,int p,int q){
if(p<=l&&r<=q)return max(hmx_st[id],hmx_es[id]);
PushDown(id,l,r);
int mid=(l+r)>>1;
if(q<=mid)return Hmx(lid,l,mid,p,q);
else if(p>mid)return Hmx(rid,mid+1,r,p,q);
else return max(Hmx(lid,l,mid,p,q),Hmx(rid,mid+1,r,p,q));
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
SGT::Build(1,1,n);
while(m--){
int o,l,r,x;
cin>>o>>l>>r;
switch(o){
case 1:{
cin>>x;
SGT::Add(1,1,n,l,r,x);
break;
}case 2:{
cin>>x;
SGT::Min(1,1,n,l,r,x);
break;
}case 3:{
cout<<SGT::Sum(1,1,n,l,r)<<"\n";
break;
}case 4:{
cout<<SGT::Mx(1,1,n,l,r)<<"\n";
break;
}default:{
cout<<SGT::Hmx(1,1,n,l,r)<<"\n";
break;
}
}
}
return 0;
}