线段树:区间历史和 & 区间历史最值 & 区间最值操作
线段树:区间历史和 & 区间历史最值 & 区间最值操作
区间历史和
例题:Loj#193.线段树历史和。
一个数列,需要支持区间加、区间求和、区间求历史和。
矩阵乘法
每个点存
区间加
求
那么维护区间向量和、懒标记维护转移矩阵的积即可。
矩阵转化为标记
由于转移的方向为
所以设转移矩阵上三角的值,计算相乘后的值。
所以可以用三个数表示一个标记,标记
而对于更新信息则有:
考虑实际意义:
通过上面的过程可以看出,矩阵乘法和标记转移本质是相同的,不过直接矩阵乘法有很多无用的乘法,常数较大,可以通过取出那些有用的值转化为标记。
通过记录。
区间历史最值
例题:CPU 监控。
一个数列,支持区间加、区间赋值、区间最值、区间历史最值。
矩阵乘法
考虑设
运用广义矩阵乘法:
把信息写成行向量,右乘转移矩阵。我们需要保留一项
区间加
区间赋值
维护区间的向量
矩阵转化为标记
注意到转移的方向为
所以我们需要维护
那么更新信息就有:
通过记录。注意当若干 long long
的范围,所以小于 check
函数实现这个功能。
懒标记与信息的封装的示例代码:
const int inf=1e16; int check(int x) { if(x<-inf) x=-inf; if(x>inf) x=inf; return x; } struct tag{ int a,b,c,d; tag operator+(tag x) { return (tag){ check(a+x.a), max(check(a+x.b),b), max(check(c+x.a),x.c), max(check(c+x.b),max(x.d,d)) }; } }; struct arr { int s,h; arr operator+(arr x) {return (arr){max(s,x.s),max(h,x.h)};} arr operator*(tag x) {return (arr){max(check(s+x.a),x.c),max(check(s+x.b),max(h,x.d))};} };
区间最值操作
例题:线段树 3。
修改:区间加,区间 chkmin。
查询:区间和,区间最大值,区间历史最大值。
不带赋值操作的区间历史最大值
把上一节中矩阵的第三行与第三列删去即可得到此时的转移矩阵。
设
更新信息为
加入最值操作
线段树显然不能直接区间取
我们对一个区间取
具体来说我们维护三个值:区间最大值
- 当
时,不需要操作。 - 当
,更新信息,打上标记并返回,发现此时 变为 , 不变。 - 当
时,此时不能更新继续递归。
根据势能分析证明,当只有最值操作时复杂度是
我们需要对最大值和非最大值分别维护上述两种标记,即加标记与加标记的历史最大值。
可以想到,下传标记时需要看子区间最大值是否是当前区间的最大值,然后选择下传最大值的标记或非最大值的标记。
但是这里有坑点:选择下传最大值或非最大值的标记时,不能直接用两个区间的最大值比较,因为可能其中一个下传了标记,一个没有下传标记,因此我们可以在合并时记录
时间复杂度
具体实现
可以将懒标记封装起来,用重载运算符定义合并。
struct tag{ int a,b; tag operator+(tag x) { return (tag){a+x.a,max(a+x.b,b)}; } };
一些定义。h
是历史最值,s
是区间和,t1
和 t2
是最大值和非最大值的标记。
int mx[N*4],ms[N*4],h[N*4],cnt[N*4],s[N*4],from[N*4]; tag t1[N*4],t2[N*4];
合并信息与下传标记。注意下传完后要把懒标记清空。这里 from[x]==0
时则说明两个子区间都是最大值,否则 from[x]
就是儿子的编号。
void pushup(int x) { s[x]=s[ls]+s[rs]; h[x]=max(h[ls],h[rs]); if(mx[ls]==mx[rs]) { mx[x]=mx[ls]; cnt[x]=cnt[ls]+cnt[rs]; ms[x]=max(ms[ls],ms[rs]); from[x]=0; return; } int c1=ls,c2=rs; if(mx[c1]<mx[c2]) swap(c1,c2); mx[x]=mx[c1],ms[x]=max(ms[c1],mx[c2]),cnt[x]=cnt[c1]; from[x]=c1; } void pushdown(int x,int l,int r) { if(l<r) { if(ls==from[x]||!from[x]) t1[ls]=t1[ls]+t1[x]; else t1[ls]=t1[ls]+t2[x]; if(rs==from[x]||!from[x]) t1[rs]=t1[rs]+t1[x]; else t1[rs]=t1[rs]+t2[x]; t2[ls]=t2[ls]+t2[x],t2[rs]=t2[rs]+t2[x]; } h[x]=max(h[x],mx[x]+t1[x].b); s[x]+=cnt[x]*t1[x].a; mx[x]+=t1[x].a; s[x]+=(r-l+1-cnt[x])*t2[x].a; if(ms[x]!=-inf) ms[x]+=t2[x].a; t1[x]=t2[x]=(tag){0,0}; }
修改的写法。注意由于取
void chkmin(int x,int l,int r,int L,int R,int y) { pushdown(x,l,r); if(R<l||r<L) return; if(L<=l&&r<=R) { if(mx[x]<=y) return; if(ms[x]<y) { t1[x]=t1[x]+(tag){y-mx[x],y-mx[x]}; pushdown(x,l,r); return; } chkmin(ls,l,mid,L,R,y),chkmin(rs,mid+1,r,L,R,y); pushup(x); return; } chkmin(ls,l,mid,L,R,y),chkmin(rs,mid+1,r,L,R,y); pushup(x); } void add(int x,int l,int r,int L,int R,int y) { if(L<=l&&r<=R) { t1[x]=t1[x]+(tag){y,y}; t2[x]=t2[x]+(tag){y,y}; pushdown(x,l,r); return; } pushdown(x,l,r); if(R<l||r<L) return; add(ls,l,mid,L,R,y),add(rs,mid+1,r,L,R,y); pushup(x); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App