吉司机线段树
upd on 2023.3.25:终于补完了。马上省选了还不会这个。
看到今天他们A层邀请赛整了一个于是决定多少看看。
来源:吉如一老师2016年国家集训队论文《区间最值操作与历史最值问题》。oiwiki上的论文没有粘全,最后的四类讨论只有一类(而且没有精髓
首先线段树显然不用再说,直接进入正题。
区间最值问题
区间最值问题,也就是对序列
我们对于每个节点维护区间和
- 如果
,说明 无法更新答案,直接退出。 - 如果
,说明x可以更新所有最大值而不改变次大值,直接修改 ,减去 ,更新 为 ,打上标记退出。 - 其他情况,递归向下。
代码很好写,大力分讨即可。复杂度玄学,
#define lson rt<<1
#define rson rt<<1|1
struct node{
int l,r,max,se,lazy,sum,cnt;
}tree[800010];
int n,ans,mex[200010],a[200010],nxt[200010],pos[200010];
bool v[200010];
void pushup(int rt){
tree[rt].sum=tree[lson].sum+tree[rson].sum;
if(tree[lson].max==tree[rson].max){
tree[rt].max=tree[lson].max;
tree[rt].se=max(tree[lson].se,tree[rson].se);
tree[rt].cnt=tree[lson].cnt+tree[rson].cnt;
}
else if(tree[lson].max>tree[rson].max){
tree[rt].max=tree[lson].max;
tree[rt].se=max(tree[lson].se,tree[rson].max);
tree[rt].cnt=tree[lson].cnt;
}
else{
tree[rt].max=tree[rson].max;
tree[rt].se=max(tree[rson].se,tree[lson].max);
tree[rt].cnt=tree[rson].cnt;
}
}
void pushmax(int rt,int val){
if(tree[rt].max<=val)return;
tree[rt].sum-=(tree[rt].max-val)*tree[rt].cnt;
tree[rt].max=tree[rt].lazy=val;
}
void pushdown(int rt){
if(tree[rt].lazy==-1)return;
pushmax(lson,tree[rt].lazy);pushmax(rson,tree[rt].lazy);
tree[rt].lazy=-1;
}
void build(int rt,int l,int r){
tree[rt].l=l;tree[rt].r=r;tree[rt].lazy=-1;
if(l==r){
tree[rt].sum=tree[rt].max=a[l];
tree[rt].se=-1;tree[rt].cnt=1;
return;
}
int mid=(l+r)>>1;
build(lson,l,mid);build(rson,mid+1,r);
pushup(rt);
}
void update(int rt,int l,int r,int val){
if(tree[rt].max<=val)return;
if(l<=tree[rt].l&&tree[rt].r<=r&&tree[rt].se<val){
pushmax(rt,val);return;
}
int mid=(tree[rt].l+tree[rt].r)>>1;
pushdown(rt);
if(l<=mid)update(lson,l,r,val);
if(mid<r)update(rson,l,r,val);
pushup(rt);
}
int query(int rt,int l,int r){
if(l<=tree[rt].l&&tree[rt].r<=r)return tree[rt].sum;
int mid=(tree[rt].l+tree[rt].r)>>1,val=0;
pushdown(rt);
if(l<=mid)val+=query(lson,l,r);
if(mid<r)val+=query(rson,l,r);
return val;
}
如果我们再加一个操作:区间加减,会怎样呢?
其实可以像之前一样搞。具体地,我们在下传标记的时候,首先下传加减标记再下传大小标记。同时,加减的时候可以更新大小的标记。啥?你问我大小对加减的影响?
讨论操作顺序。
- 先加减后取最值,最值操作会把加减的影响覆盖掉。
- 先最值后加减,加减会改变最值操作的标记。
所以是正确的。代码在此不表。(其实是我发懒)
吉老师证明了这个的复杂度是
类似的,同时维护区间
下面是几个论文中的例题:
- 开头说的他们A层的考试题:就是这两个操作再多加一个询问某个元素修改了多少次。其实也好搞,另外建一棵线段树,每次区间加的时候同样的区间修改,每次下传区间最值标记的时候修改一次(不带标记),也就是什么时候下放什么时候改。查询的时候继续下传区间最值标记就行了。
- 给你两个数组
,同时支持区间最小和区间加,询问区间 的最大值。将位置分四类, 中都是最大值, 中最大 中非最大, 中最大 中非最大, 中都非最大。四类大力分讨,代码又臭又长就免了。 - 区间最小,区间加,区间
。首先我们回忆不带区间最值操作的 怎么搞:整一个差分序列(由更相减损法可得,差分序列的 不改变),于是区间操作就变成了单点操作,可以递归到最底层修改然后pushup,最后求出区间 之后再与 区间和(也就是原数) 一下就行了。现在我们加上了区间取最小值。(有点长让我分一段)
论文中提到将最大值单独扔出来,非最大值差分维护。具体的,维护区间最大值
区间下传标记和修改就按照上面提到的搞。考虑如何pushup。首先我们观察到差分数组的顺序是不必要的,所以我们不必关注
限于篇幅,只讨论
终于写完第一类了(
历史最值问题
大概分三类:
- 历史最大值:定义辅助数组
,初始与 相同。每次操作后,使所有 ,此时 称为 的历史最大值。 - 历史最小值:定义类似历史最大值。
- 历史版本和:
初始全为 ,每次修改后使所有 加上 ,此时 称为 的历史版本和。
可以用懒标记处理的问题
以区间最大值为例。例题:P4314 CPU 监控。
考虑一个简单的问题:区间加,区间最大值,区间历史最大值。
定两个标记
如果再加个区间覆盖?存个当前覆盖标记和历史最大覆盖标记就行了。注意覆盖的时候再区间加可以直接加标记。
无法用懒标记处理的单点问题
例题:【模板】线段树 3。
题意:区间加、区间取
回忆区间最值操作的做法,我们将区间最值操作转化为对最值的区间加减操作。这个题也可以分为最大值和非最大值分别操作,每个像上一题一样上两个标记。
代码就是把两个插一块。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)&&ch!='-')ch=getchar();
if(ch=='-')f=-1,ch=getchar();
while(isdigit(ch))x=10*x+ch-'0',ch=getchar();
return x*f;
}
int n,m,a[500010];
struct node{
long long sum;
int l,r,mxa,mxb,se,cnt,lz1,lz2,lz3,lz4;
}tree[2000010];
void pushup(int rt){
tree[rt].sum=tree[lson].sum+tree[rson].sum;
tree[rt].mxa=max(tree[lson].mxa,tree[rson].mxa);
tree[rt].mxb=max(tree[lson].mxb,tree[rson].mxb);
if(tree[lson].mxa==tree[rson].mxa){
tree[rt].se=max(tree[lson].se,tree[rson].se);
tree[rt].cnt=tree[lson].cnt+tree[rson].cnt;
}
else if(tree[lson].mxa>tree[rson].mxa){
tree[rt].se=max(tree[lson].se,tree[rson].mxa);
tree[rt].cnt=tree[lson].cnt;
}
else{
tree[rt].se=max(tree[lson].mxa,tree[rson].se);
tree[rt].cnt=tree[rson].cnt;
}
}
void pushtag(int rt,int val1,int val2,int val3,int val4){
tree[rt].sum+=1ll*val1*tree[rt].cnt+1ll*val2*(tree[rt].r-tree[rt].l+1-tree[rt].cnt);
tree[rt].mxb=max(tree[rt].mxb,tree[rt].mxa+val3);
tree[rt].mxa+=val1;
if(tree[rt].se!=-2147483647)tree[rt].se+=val2;
tree[rt].lz3=max(tree[rt].lz3,tree[rt].lz1+val3);
tree[rt].lz4=max(tree[rt].lz4,tree[rt].lz2+val4);
tree[rt].lz1+=val1;tree[rt].lz2+=val2;
}
void pushdown(int rt){
int mx=max(tree[lson].mxa,tree[rson].mxa);
if(tree[lson].mxa==mx)pushtag(lson,tree[rt].lz1,tree[rt].lz2,tree[rt].lz3,tree[rt].lz4);
else pushtag(lson,tree[rt].lz2,tree[rt].lz2,tree[rt].lz4,tree[rt].lz4);
if(tree[rson].mxa==mx)pushtag(rson,tree[rt].lz1,tree[rt].lz2,tree[rt].lz3,tree[rt].lz4);
else pushtag(rson,tree[rt].lz2,tree[rt].lz2,tree[rt].lz4,tree[rt].lz4);
tree[rt].lz1=tree[rt].lz2=tree[rt].lz3=tree[rt].lz4=0;
}
void build(int rt,int l,int r){
tree[rt].l=l;tree[rt].r=r;
if(l==r){
tree[rt].sum=tree[rt].mxa=tree[rt].mxb=a[l];
tree[rt].cnt=1;tree[rt].se=-2147483647;
return;
}
int mid=(l+r)>>1;
build(lson,l,mid);build(rson,mid+1,r);
pushup(rt);
}
void updatesum(int rt,int l,int r,int val){
if(l<=tree[rt].l&&tree[rt].r<=r){
tree[rt].sum+=1ll*val*(tree[rt].r-tree[rt].l+1);
tree[rt].mxa+=val;tree[rt].mxb=max(tree[rt].mxb,tree[rt].mxa);
if(tree[rt].se!=-2147483647)tree[rt].se+=val;
tree[rt].lz1+=val;tree[rt].lz2+=val;
tree[rt].lz3=max(tree[rt].lz3,tree[rt].lz1);
tree[rt].lz4=max(tree[rt].lz4,tree[rt].lz2);
return;
}
pushdown(rt);
int mid=(tree[rt].l+tree[rt].r)>>1;
if(l<=mid)updatesum(lson,l,r,val);
if(mid<r)updatesum(rson,l,r,val);
pushup(rt);
}
void updatemin(int rt,int l,int r,int val){
if(val>=tree[rt].mxa)return;
if(l<=tree[rt].l&&tree[rt].r<=r&&tree[rt].se<val){
val=tree[rt].mxa-val;
tree[rt].sum-=1ll*tree[rt].cnt*val;
tree[rt].mxa-=val;tree[rt].lz1-=val;
return;
}
pushdown(rt);
int mid=(tree[rt].l+tree[rt].r)>>1;
if(l<=mid)updatemin(lson,l,r,val);
if(mid<r)updatemin(rson,l,r,val);
pushup(rt);
}
long long querysum(int rt,int l,int r){
if(l<=tree[rt].l&&tree[rt].r<=r)return tree[rt].sum;
pushdown(rt);
int mid=(tree[rt].l+tree[rt].r)>>1;
long long val=0;
if(l<=mid)val+=querysum(lson,l,r);
if(mid<r)val+=querysum(rson,l,r);
return val;
}
int querymxa(int rt,int l,int r){
if(l<=tree[rt].l&&tree[rt].r<=r)return tree[rt].mxa;
pushdown(rt);
int mid=(tree[rt].l+tree[rt].r)>>1,val=-2147483647;
if(l<=mid)val=max(val,querymxa(lson,l,r));
if(mid<r)val=max(val,querymxa(rson,l,r));
return val;
}
int querymxb(int rt,int l,int r){
if(l<=tree[rt].l&&tree[rt].r<=r)return tree[rt].mxb;
pushdown(rt);
int mid=(tree[rt].l+tree[rt].r)>>1,val=-2147483647;
if(l<=mid)val=max(val,querymxb(lson,l,r));
if(mid<r)val=max(val,querymxb(rson,l,r));
return val;
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++)a[i]=read();
build(1,1,n);
while(m--){
int od=read(),l=read(),r=read(),val;
if(od==1){
val=read();updatesum(1,l,r,val);
}
else if(od==2){
val=read();updatemin(1,l,r,val);
}
else if(od==3)printf("%lld\n",querysum(1,l,r));
else if(od==4)printf("%d\n",querymxa(1,l,r));
else printf("%d\n",querymxb(1,l,r));
}
return 0;
}
无区间最值操作的区间问题
主要三个问题:区间加减,区间历史最大值/历史最小值/历史版本和的和。
事实上这三个问题都可以被转化成简单的区间加减问题。以下设
-
区间最小值:维护一个数组
,那么我们只要查区间的 。 好办,关于 ,容易发现每次区间加 都是把 变成 。 -
区间最大值:同上,维护
,此时区间加 即为把 变为 。 -
历史版本和:设当前为第
次操作,维护 。对于区间加 ,相当于给 减掉 。那么查询就相当于查 。
有区间最值操作的区间问题
jiry_2 老师搞的 Segment Beats。
三个操作:区间取
依据上边,我们维护
上边两个操作相当于:对区间
仍然像区间最值一样拆开,拆成
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)