浅谈线段树

PART 1 关于线段树
线段树是用来做一些对序列更改与查询的问题的。
它可以支持的更改有区间修改、单点修改
它可以支持的查询有区间最大/小值、区间和等等(本文将用区间和为例)

PART 2 线段树的原理
线段树就是先建一棵二叉树,每个节点保存的是要求的东西(如区间最大/小值、区间和等等)
每个叶子节点的值就是对应原序列中数的值,每个节点都有代表的一段原序列区间,当我们修改/查询时,只需要将要修改/查询的区间找出来即可。当我们一层一层回溯时,就可以通过两个儿子的值来维护当前节点的值,不需要一个一个的修改/查询,从而使时间复杂度降低至 O ( ( N + M ) l o g N ) O((N+M)log N) O((N+M)logN)

PART 3 建树
建树的过程就是一直递归到叶子节点,将叶子节点赋值,在回溯的同时维护当前节点的值
注:在用数组模拟二叉树时,通常用x2表示x的左儿子,用x2+1表示x的右儿子

#define ls(x) x<<1//位运算,相当于x*2
#define rs(x) x<<1|1//位运算,相当于x*2+1
void build(ll x,ll l,ll r)//x是当前节点的编号,l和r是左右区间
{
	if (l==r)//如果当前左右区间相等(到达叶子节点)
	{
		t[x].sum=f[l];//当前节点的值就是原序列中对应位置的值
		return;//返回
	}
	ll mid=(l+r)>>1;//位运算,相当于(l+2)/2,此处定义mid用于二分
	build(ls(x),l,mid);//递归左子区间
	build(rs(x),mid+1,r);//递归右子区间
	update(x);
}

接下来你们应该会问update(x)是什么意思,update(x)的作用就是维护节点x的值正确

void update(ll v){t[v].sum=t[ls(v)].sum+t[rs(v)].sum;}

PART 4 区间修改
这里就要引入线段树最重要的东西——懒标记
如果我们要直接修改,每次都修改到每个与需要修改的区间有关的节点,那么每一次操作的时间复杂度可能会退化成 O ( N l o g N ) O(N log N) O(NlogN)

懒标记的作用是我们对找到的区间不直接修改,而是在代表这段区间的节点打上懒标记,等到查询或修改更下层的值时才下传懒标记。
Q:既然迟早要下传懒标记,那么为什么要懒标记呢?每次修改直接修改到底不就可以了吗?
A:懒标记的作用就是延迟。如果我们先修改100000次再查询1次呢?直接修改就需要 O ( 100000 N l o n g N ) O(100000N long N) O(100000NlongN)的时间复杂度,而如果用了懒标记,就可以只下传1次,时间复杂度只有 O ( 100000 l o g N ) O(100000log N) O(100000logN)

void modify(ll x,ll l,ll r,ll a,ll b,ll c)//x,l,r所代表的和建树时一样,
//a,b,c代表a~b的区间同时加上c
{
	if (a<=l && r<=b)//线段树节点代表的区间不一定刚好代表a~b的区间,
	//只要当前区间被包含在a~b中,那么当前节点的值就要完全被修改
	{
		t[x].sum+=(r-l+1)*c;//因为当前节点所代表的是一段区间,
		//而这个区间的每一个数都要被修改,所以要加上区间长度*c
		t[x].tag+=c;//打上懒标记
		return;
	}
	if (l>b || r<a) return;//很重要,如果当前区间完全与所要修改的区间无关,那么返回
	//这能将每次操作的时间复杂度稳定在O(log N)
	pushdown(x,l,r);//下传懒标记
	ll mid=(l+r)>>1;
	modify(ls(x),l,mid,a,b,c);//递归左子区间
	modify(rs(x),mid+1,r,a,b,c);//递归右子区间
	update(x);//维护当前节点的值
}

这里就要牵扯到一个过程——pushdown

void pushdown(ll v,ll l,ll r)//v是要下传懒标记的节点,l和r是所代表的区间
{
	if (t[v].tag==0) return;//如果没有懒标记就不需要下传
	ll vv=t[v].tag,vl=ls(v),vr=rs(v);//方便做题
	ll mid=(l+r)>>1;
	t[vl].sum+=(mid-l+1)*vv;//因为这个区间的每一个数都要被修改,
	t[vr].sum+=(r-mid)*vv;//所以要加上区间长度*下传的懒标记
	t[vl].tag+=vv;//儿子节点接收懒标记
	t[vr].tag+=vv;
	t[v].tag=0;//一定要记得清空已下传懒标记
}

PART 5 区间查询
区间查询和区间修改的思路基本一致

void query(ll x,ll l,ll r,ll x,ll y)//思路一模一样,请自行理解
{
	if (x<=l && r<=y)//如果当前区间被包含在所求区间中
	{
		ans+=t[x].sum;//则累加当前节点的值
		return;
	}
	if (r<x || l>y) return;
	pushdown(x,l,r);
	ll mid=(l+r)>>1;
	query(ls(x),l,mid,x,y);
	query(rs(x),mid+1,r,x,y);
}

PART 6 总结
本篇只讲述线段树的区间修改/查询,原因是如果需要单点修改/查询,只需要在修改/查询时改成

modify(1,1,n,x,x,y)

query(1,1,n,x,x);

即可。

希望能帮到您!

代码:(洛谷P3372【模板】线段树1)

#include<cstdio>
#define ll long long
using namespace std;
const int N=100010;
struct node
{
	ll tag,l,r,sum;
}t[N<<2];
ll q,n,o,x,y,z,ans;
ll a[N];
ll ls(ll x){return x<<1;}
ll rs(ll x){return x<<1|1;}
void update(ll ln){t[ln].sum=t[ls(ln)].sum+t[rs(ln)].sum;}
void pushdown(ll ql,ll qr,ll ln)
{
	if (t[ln].tag==0) return;
	ll mid=(ql+qr)>>1;
	t[ls(ln)].sum+=(mid-ql+1)*t[ln].tag;
	t[rs(ln)].sum+=(qr-(mid+1)+1)*t[ln].tag;
	t[ls(ln)].tag+=t[ln].tag;
	t[rs(ln)].tag+=t[ln].tag;
	t[ln].tag=0;
}
void build(ll lq,ll rq,ll now)
{
	if (lq==rq)
	{
		t[now].l=lq;
		t[now].r=rq;
		t[now].sum=a[lq];
		return;
	}
	ll mid=(lq+rq)>>1;
	build(lq,mid,ls(now));
	build(mid+1,rq,rs(now));
	t[now].l=lq;
	t[now].r=rq;
	update(now);
}
void add(ll lq,ll rq,ll x,ll y,ll z,ll now)
{
	if (x<=lq && rq<=y)
	{
		t[now].sum+=(rq-lq+1)*z;
		t[now].tag+=z;
		return;
	}
	if (rq<x || lq>y) return;
	pushdown(lq,rq,now);
	ll mid=(lq+rq)>>1;
	add(lq,mid,x,y,z,ls(now));
	add(mid+1,rq,x,y,z,rs(now));
	update(now);
}
void query(ll lq,ll rq,ll x,ll y,ll now)
{
	if (x<=lq && rq<=y)
	{
		ans+=t[now].sum;
		return;
	}
	if (rq<x || lq>y) return;
	pushdown(lq,rq,now);
	ll mid=(lq+rq)>>1;
	query(lq,mid,x,y,ls(now));
	query(mid+1,rq,x,y,rs(now));
}
int main()
{
	scanf("%lld %lld",&n,&q);
	for (int i=1;i<=n;i++) scanf("%lld",&a[i]);
	build(1,n,1);
	for (int i=1;i<=q;i++)
	{
		scanf("%lld",&o);
		if (o==1)
		{
			scanf("%lld %lld %lld",&x,&y,&z);
			add(1,n,x,y,z,1);	
		} 
		if (o==2)
		{
			ans=0;
			scanf("%lld %lld",&x,&y);
			query(1,n,x,y,1);	
			printf("%lld\n",ans);
		} 
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值