线段树学习笔记
线段树学习笔记
对线段树的介绍
- 线段树是一种树形数据结构,属于二叉树
- 一棵线段树的每个结点都可以用区间
来表示。线段树的叶结点表示的区间为 或 。 - 线段树支持单点修改,区间修改,区间和/区间最值等的查询。
- 对于线段树的区间修改一般采用延迟修改方式
- 线段树去掉最后一层后是一棵满二叉树
线段树的建树
线段树的表示
因为线段树是一棵二叉树,所以可以采用与二叉堆一致的表示法:
编号为p的结点的
- 左结点的编号为
- 右结点的编号为
类似二叉堆,线段树是一个结构体数组。
C++ - 6 行
struct SegmentTree
{
int l,r;// 表示该结点代表的区间
int dat;//区间[l,r]的最大值
int add;//延迟修改标记
}
线段树的结点
为了避免最最让人难受的RE,所以考虑一棵线段树的最大结点数。
设现在需要表示区间
由线段树的性质得知,该二叉树有
因为我的个人习惯,我一般会不处理一个数组的第一个元素,即下标为
C++ - 27 行
#include<bits/stdc++.h>
using namespace std;
struct SegmentTree
{
int l,r;// 表示该结点代表的区间
int dat;//区间[l,r]的最大值
int add;//延迟修改标记
} t[4*1000001];
int va[114514];//区间是数组下标区间,这是线段树叶结点实际代表的值
void bulid(int l,int r,int p)// 结点的数组下标为p,表示区间[l,r]
{
t[p].l = l;//代表区间初始化
t[p].r = r;
if(l == r)//叶结点
{
t[p].dat = va[l];
return;// 如果你想RE,建议删去这行
}
int mid = (t[p].l + t[p].r) / 2;
bulid(l,mid,p*2);// 左子结点
bulid(mid+1,r,p*2+1);//右子结点
t[p].dat = max(t[p*2].dat,t[p*2+1].dat);//自下往上更新数据
}
int main()
{
//略
}
线段树的区间修改&延迟修改
区间修改
对于访问到的每一个结点,如果修改范围完全包括了它,那么直接修改+打延迟修改标记。否则:
-
若修改范围与该结点的左结点有重合,则递归修改左结点
-
若修改范围与该结点的右结点有重合,则递归修改右结点
C++ - 20 行
#include<bits/stdc++.h>
using namespace std;
void change(int l,int r,int add,int p)//add 为区间修改所要加上的值
{
if(l <= t[p].l && t[p].r <= r)
{
t[p].add += add;
t[p].dat += add;
return ;
}
spread(p);
int mid = (t[p].l + t[p].r) / 2;
if(l <= mid)
{
change(l,r,add,p*2);
}
if(mid < r)
{
change(l,r,add,p*2+1);
}
}
延迟修改
延迟修改的含义并不是当访问到该结点时在进行修改,而是在访问到它时,它已经被修改,若其不是叶结点,则给它的两个子结点,进行对应修改并打上标记,代表该结点已被修改,但其子结点没有被修改。
C++ - 16 行
#include<bits/stdc++.h>
using namespace std;
void spread(int p)// p为父结点下标
{
if(t[p].add)
{
t[p*2].add = t[p*2+1].add = t[p].add;//给子结点打标记
t[p*2].dat += t[p].add;
t[p*2+1].dat += t[p].add;//因为是最大值,所以直接加上就行
t[p].add = 0;//删除标记
}
}
int main()
{
//略
}
线段树的区间查询
对于访问到的每一个结点,如果查询范围完全包括了它,那么直接返回。否则:
-
若查询范围与该结点的左结点有重合,则递归查询左结点
-
若查询范围与该结点的右结点有重合,则递归查询右结点
C++ - 18 行
#include<bits/stdc++.h>
using namespace std;
long long ask(int l,int r,int p)
{
if(l <= t[p].l && t[p].r <= r)
{
return t[p].dat;
}
spread(p);
int mid = (t[p].l + t[p].r) / 2;
long long dat=0x8000000000000000;
if(l <= mid)
{
dat=max(dat,ask(l,r,p*2));
}
if(mid < r)
{
dat=max(dat,ask(l,r,p*2+1));
}
return dat;
}
The end
完整代码
#include<bits/stdc++.h>
using namespace std;
struct SegmentTree
{
int l,r;// 表示该结点代表的区间
long long dat;//区间[l,r]的最大值
long long add;//延迟修改标记
} t[4*1000001];
long long va[114514];//区间是数组下标区间,这是线段树叶结点实际代表的值
void bulid(int l,int r,int p)// 结点的数组下标为p,表示区间[l,r]
{
t[p].l = l;//代表区间初始化
t[p].r = r;
if(l == r)//叶结点
{
t[p].dat = va[l];
return;// 如果你想RE,建议删去这行
}
int mid = (l + r) / 2;
bulid(l,mid,p*2);// 左子结点
bulid(mid+1,r,p*2+1);//右子结点
t[p].dat=max(t[p*2].dat , t[p*2+1].dat );//自下往上更新数据
}
void spread(int p)// p为父结点下标
{
if(t[p].add )
{
t[p*2].add += t[p].add;
t[p*2+1].add += t[p].add;//给子结点打标记
t[p*2].dat += t[p].add;
t[p*2+1].dat += t[p].add;//因为是最大值,所以直接加上就行
//删除标记
}
t[p].add = 0;
}
void change(int l,int r,int add,int p)//add 为区间修改所要加上的值
{
if(l <= t[p].l && t[p].r <= r)
{
t[p].add += add;
t[p].dat += add;
return ;
}
spread(p);
int mid = (t[p].l + t[p].r) / 2;
if(l <= mid)
{
change(l,r,add,p*2);
}
if(mid < r)
{
change(l,r,add,p*2+1);
}
t[p].dat=max(t[p*2].dat , t[p*2+1].dat );
}
long long ask(int l,int r,int p)
{
if(l <= t[p].l && t[p].r <= r)
{
return t[p].dat;
}
spread(p);
int mid = (t[p].l + t[p].r) / 2;
long long dat=0x8000000000000000;
if(l <= mid)
{
dat=max(dat,ask(l,r,p*2));
}
if(mid < r)
{
dat=max(dat,ask(l,r,p*2+1));
}
return dat;
}
int main()
{
int n,m;
cin>>n>>m;
bulid(1,n,1);//建树
for(int i=1,op,l,r,add;i<=m;i++)//op为操作类型,[l,r]表示区间,add为修改加上的值
{
cin>>op>>l>>r;
if(op==1)
change(l,r,add,1);
if(op==2)
{
cin>>add;
cout<<ask(l,r,1)<<endl;
}
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)