进阶线段树
线段树:
这是一个线段树的例图。
我们可以发现其实线段树就是将几个连续的小区间拼凑成一个个大区间的过程,从而实现查找时的优秀的复杂度。
引入-分块基础的思想:
其实如果你知道分块的思想,那么你会更好理解线段树的思想,分块就是将一个序列分成
线段树的懒标记:
既然我们知道了线段树的懒标记的大致的作用那么我们该如何去实现呢?
其实不难就是在查询和修改时多了一些维护的东西。
常见的修改题型有以下几种:
- 将区间
的数加 。 - 将区间
的数乘 。 - 将区间
的数统一赋值为 。 - 将区间
的数统一开平方。 - 将区间
的值统一都取反(仅用于数组内所有数都为0|1
)
常见的查询题型有以下几种:
- 输出
- 输出
中的最大值。 - 输出
中的最小值。 - 输出
中的所有数的乘积。
今天我们主要围绕这几个方面进行讲解
例题1
首先先看修改操作:
我们分析下这个对于区间 t
数组表示总和,用 lz
数组表示懒标记。
那么对于
1.修改时如果是当前的区间完全覆盖线段树某一个节点所对应的区间,那么就将这个节点的
2.如果是零块,那么就将这个节点的信息传到他的左右儿子节点,即 pushdown
函数,因为这样可以方便零块去继续递归继续修改。
由于线段树为
以上就是修改操作的核心。
再看查询操作:
设查询的区间为
查询的细节:
查询时应该查询什么值呢?
肯定是查询
需要注意的时查询时也需要将标记下传。
查询相对于修改就好理解了许多。
例题1-tag&pushdown部分的代码:
void tag(int p,int l,int r,ll k){
t[p]+=(r-l+1)*k;
lz[p]+=k;
}
void pushdown(int p,int l,int r){
int mid=(l+r)>>1;
tag(ls(p),l,mid,lz[p]);
tag(rs(p),mid+1,r,lz[p]);
lz[p]=0;
}
因为前面已经讲过如何修改了,所以这里不再复述。
例题2
修改部分:
根据数学知识我们知道
设区间总值为 1
而不是 0
。
查询自然和线段树 1
相同,不再复述。
例题2-tag&pushdown 代码:
void tag(int p,int l,int r,ll k,bool add){
/*add表示是否为加法标记*/
if(add){
t[p].t=(t[p].t+(r-l+1)*k%m)%m;
/*总和变化*/
t[p].z1=(t[p].z1+k)%m;
/*加法标记变化*/
}else{
t[p].t=t[p].t*k%m;
/*总和变化*/
t[p].z1=t[p].z1*k%m;
t[p].z2=t[p].z2*k%m;
/*加法标记与乘法标记都乘k*/
}
}
void push_down(int p,int l,int r){
int mid=(l+r)>>1;
tag(ls(p),l,mid,t[p].z2,false);
tag(rs(p),mid+1,r,t[p].z2,false);
t[p].z2=1;
/*下传乘法标记*/
tag(ls(p),l,mid,t[p].z1,true);
tag(rs(p),mid+1,r,t[p].z1,true);
t[p].z1=0;
/*下传加法标记*/
}
例题3
这道题涉及到区间赋值的问题。
设原来的子树总和为
单本题让我们维护区间最值,这个更简单,直接将最值改为
例题3-tag&pushdown 代码:
void tag(int p,int l,int r,ll k,int o){
if(o==1){
/*区间赋值*/
t[p]=k;
z1[p]=0;z2[p]=k;
}else{
/*区间加法*/
t[p]+=k;
z1[p]+=k;
}
}
void push_down(int p,int l,int r){
int mid=(l+r)>>1;
if(z2[p]!=NONE){
/*有区间赋值标记*/
tag(ls(p),l,mid,z2[p],1);
tag(rs(p),mid+1,r,z2[p],1);
z2[p]=NONE;
}
if(z1[p]){
/*有区间加法标记*/
tag(ls(p),l,mid,z1[p],2);
tag(rs(p),mid+1,r,z1[p],2);
z1[p]=0;
}
}
简单提一下区间取反和区间开平方:
区间取反:
对于区间取反会有一个特点,0
变成 1
反之变成 0
所以统计答案的时候应该把
区间开方:
本题的操作是区间开方和区间查询,但区间开方不能直接用 Lazy-Tag 实现。本题的关键是:一个数如果被开方7次,那么它一定会变为1,后面再怎么开方也还是1。于是我们可以记录一个区间中是否有不为1的数,在修改时,如果都为1,直接跳过。
线段树在其他算法中在组合应用:
树链剖分算法:
树链剖分是一个常见的线段树与重链之间的组合的算法,他的思想是对于每一条链和一个子树,它在线段树上的标号总是连续的因此就可以用线段树的相关知识去实现快速的加减求和。
树链剖分中使用线段树修改查询部分的代码:
inline void update1(int x,int y,int k){
while(t[x].tot!=t[y].tot){
if(t[t[x].tot].d<t[t[y].tot].d)swap(x,y);
seg.upd(1,1,n,t[t[x].tot].id,t[x].id,k);
x=t[t[x].tot].fa;
}
if(t[x].d>t[y].d)swap(x,y);
seg.upd(1,1,n,t[x].id,t[y].id,k);
}
inline int query1(int x,int y){
int ans=0;
while(t[x].tot!=t[y].tot){
if(t[t[x].tot ].d<t[t[y].tot ].d )swap(x,y);
ans+=seg.que(1,1,n,t[t[x].tot].id,t[x].id)%mod;
x=t[t[x].tot].fa;
}
if(t[x].d>t[y].d)swap(x,y);
ans+=seg.que(1,1,n,t[x].id,t[y].id )%mod;
return ans%mod;
}
inline void update2(int x,int k){
seg.upd(1,1,n,t[x].id,t[x].id+t[x].sz-1,k);
}
inline int query2(int x){
return seg.que(1,1,n,t[x].id,t[x].id+t[x].sz-1)%mod;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!