线段树概览

大家好,你们的萝卜又双叒叕回来了!(最近在校队里写题有点累)

接下来给大家讲解线段树(话说你都开几个专题了)

Part 1:What is 线段树?

线段树是一种二叉搜索树,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。

使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(log₂N)。而未优化的空间复杂度为2N,实际应用时一般还要开4N的数组以免越界,因此有时需要离散化让空间压缩。

看到这里你是不是还是蒙的?如果是,我们来看下面一张图:

(图片来自百度,如有侵权请联系作者还不是线段树太难画了

可以看到,每一个节点都有两个数,即这个节点代表的区间的左下标和右下标,而且每一段都是父亲节点的一半。这样就可以基于分治思想,快速有效地进行合并。

Part 2:线段树适合解决哪一类问题?

线段树的应用范围非常广泛,主要用来解决在实时更新的动态数组中的区间问题。简而言之,就是区间求解。

Part 3:如果我是初学者,线段树中的那些基本代码片段需要我学习?

(此处的部分代码片段的取名为个人喜好,其他题解里或许不一样,无需模仿)

1.创建一个线段树结构体

最基本的线段树包含:左下标,右下标,其他(如:区间和sum,懒标记lazy,最大区间和ans等)。基于这个,可以写出以下代码:

struct Tree {
	int l,r,...;
}tree[maxm];

2.初始化线段树

初始化一个线段树需要记录每个结点的左下标,右下标。编写一个函数,分别有三个变量:左下标l,右下标r,以及当前节点位于结构体中的位置id。观察上面的线段树(忘了的同学请往上翻),可以发现,根节点位于1,其两个子节点分别位于2和3。再观察位于2的这个子节点,其两个子节点分别位于5和6。由此得出,位于id的两个子节点分别位于id*2和id*2+1。如果父节点代表的区间为[l,r],其中点为mid的话,其两个子节点代表的区间分别为[l,mid]和[mid+1,r]。由此写出代码:

void build(int l,int r,int id) { // 构建线段树 
	tree[id].l=l;
	tree[id].r=r;
	if (l==r) {
        ...
		return;
    }
	int mid=(l+r)/2;
	build(l,mid,id*2);
	build(mid+1,r,id*2+1);
    ...
	return;
}

3.进行更改操作

这里需要依题意进行讨论

1.单点修改

单点修改非常简单,只需传入点位于的坐标dis,判断dis位于哪个节点内即可,这里给出模板代码(不同题目的修改操作不一样,故此这里忽略):

void push_down(int id,int dis,int ...) { // 单点修改
	...
	if (tree[id].l==tree[id].r)
		return;
	int mid=(tree[id].l+tree[id].r)/2;
	if (dis<=mid)
		push_down(id*2,k,dis);
	else
		push_down(id*2+1,k,dis);
    ...
	return;
}

2.区间修改及lazy标记

区间修改也比较简单,但是需要注意lazy标记的操作。如果当前区间被整个区间包含,那么直接更新lazy标记。然后是向下传递,如果左子节点的区间有一部分与区间重合(即tree[id*2].r>=l)那么就传递,右子节点同理。首先是更新lazy的代码:

void push_down(int id,int l,int r,ll dis) { // 更新lazy标记  
	if (tree[id].l>=l&&tree[id].r<=r) {
		...
		return; 
	}
	spread(id);
	if (tree[id*2].r>=l)
		push_down(id*2,l,r,dis);
	if (tree[id*2+1].l<=r)
		push_down(id*2+1,l,r,dis);
	...
	return; 
}

当我们遇到标记有lazy的节点时,我们需要将lazy节点下放,并根据题目更改答案,最最最重要的是归零(不归零就见祖宗)。这里给出模板(答案修改请根据题目自行编写):

void spread(int id) { // 向下传递  
	if (tree[id].lazy) {
		...
		tree[id].lazy=0; // 注意归0  
	}
	return;
}

4.回答

回答需要判断当前区间是否在提问范围内,如果在直接更新就好。否则,判断是否有重叠片段,继续搜索:

void push_up(int id,int l,int r) { // 查询答案
	if (tree[id].l>=l&&tree[id].r<=r) {
		...
		return;
	}
	...
	if (tree[id*2].r>=l)
		push_up(id*2,l,r);
	if (tree[id*2+1].l<=r)
		push_up(id*2+1,l,r);
	return;
}

Part 4:总结

总之,线段树应用广泛,但是空间占用较大,对空间复杂度要求较高的题目不建议使用。代码偏长,需要一段时间理解。

喜欢就给个赞再走吧!

  • 11
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值