线段树学习笔记

线段树学习笔记

对线段树的介绍

  1. 线段树是一种树形数据结构,属于二叉树
  2. 一棵线段树的每个结点都可以用区间[l,r]来表示。线段树的叶结点表示的区间为[l,l][r,r]
  3. 线段树支持单点修改,区间修改,区间和/区间最值等的查询。
  4. 对于线段树的区间修改一般采用延迟修改方式
  5. 线段树去掉最后一层后是一棵满二叉树

线段树的建树

线段树的表示

因为线段树是一棵二叉树,所以可以采用与二叉堆一致的表示法:

编号为p的结点的

  1. 左结点的编号为p×2
  2. 右结点的编号为p×2+1

类似二叉堆,线段树是一个结构体数组。

C++ - 6 行
struct SegmentTree
{
    int l,r;// 表示该结点代表的区间
    int dat;//区间[l,r]的最大值
    int add;//延迟修改标记
}

线段树的结点

为了避免最最让人难受的RE,所以考虑一棵线段树的最大结点数。

设现在需要表示区间[1,n],使得构造的线段树为一棵满二叉树。

由线段树的性质得知,该二叉树有n个叶结点,则总结点数为n+n÷2+n÷4+···+2+1,即4n1

因为我的个人习惯,我一般会不处理一个数组的第一个元素,即下标为0的元素。

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()
{
    //略
}

线段树的区间修改&延迟修改

区间修改

对于访问到的每一个结点,如果修改范围完全包括了它,那么直接修改+打延迟修改标记。否则:

  1. 若修改范围与该结点的左结点有重合,则递归修改左结点

  2. 若修改范围与该结点的右结点有重合,则递归修改右结点

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()
{
    //略
}

线段树的区间查询

对于访问到的每一个结点,如果查询范围完全包括了它,那么直接返回。否则:

  1. 若查询范围与该结点的左结点有重合,则递归查询左结点

  2. 若查询范围与该结点的右结点有重合,则递归查询右结点

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;
		}

	}
}

posted @   Kelojonle  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示