数据结构 - 线段树学习笔记

前言

果果终于讲线段树了
线段树太 TM 好用啦!
But,强大的功能是需要码量来实现的。

定义

线段树是一种储存了一个序列的区间信息,并在各个区间中建立了关联的数据结构。
对于任意一个序列都可以建出它的线段树。

它是一颗完全二叉树,它的每一个节点都是一个区间
对于每一个节点,其左儿子节点为这段区间的左半部分,右儿子节点为这段区间的右半部分。
而且由于是完全二叉树,其根节点的编号为 \(1\),对于每一个节点 \(p\),其左儿子节点编号为 \(2p\),右儿子节点编号为 \(2p + 1\)。这种编号方便我们遍历线段树。

如下图就是一颗线段树:

image

根据定义,这样就可以建一颗线段树:

#define ls(x) (x * 2)
#define rs(x) (x * 2 + 1)
struct node {
	int l, r, val;
	// l : 左端点
	// r : 右端点
	// val : 区间信息
}tr[N * 4];
void build(int p, int l, int r) {
	if(l == r) {tr[p] = node{l, r, \*somevalue*\};return;} // 叶节点特殊处理
	int mid = (l + r) >> 1;
	build(ls(p), l, mid); // 递归建立左右子区间
	build(rs(p), mid + 1, r);
	tr[p] = node{l, r, tr[ls(p)].val \*someopt*\ tr[rs(p)].val}; // 这里合并两个子区间的信息
}

由于线段树的树高最多为 \(\log_2n\),而且大区间的信息可以通过其子区间合并而来,就可以在 \(\text{O}(\log n)\) 的时间复杂度中实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作。

Easy-单点修改

实现单点加,区间和

其实这种玩意儿树状数组也可以做,但是线段树也可以实现。

建立一颗线段树,树上维护区间和。

修改

\([1,]n\) 开始递归,修改所有包含该点的所有区间,在其 \(val\) 上加上修改的值即可。

void updata(int p, int k, int d) {
	if(tr[p].l > k || tr[p].r < k) return; // 不包含
	tr[p].val += d;
	updata(ls(p), k, d);
	updata(rs(p), k, d);
}

查询

\([1,n]\) 开始递归,如果当前讨论的区间 \([l,r]\) 被查询区间包含,则直接返回 \([l,r]\) 的区间和。

如果 \([l,r]\) 与所讨论区间相交,那么继续递归其子树。

最后答案为所有被包含区间的区间和之和。

A:Why?
Q:因为如果取了当前讨论区间,那么不会有其他讨论的区间与其相交,因为即没有取其儿子,也没有取其祖先,直接在这里返回了值。

int sum(int p, int l, int r) {
	if(tr[p].l > r || tr[p].r < l) return 0;
	if(tr[p].l >= l && tr[p].r <= r) return tr[p].val; // 包含
	return sum(ls(p), l, r) + sum(rs(p), l , r);
}
posted @ 2024-01-09 16:55  固态H2O  阅读(16)  评论(0编辑  收藏  举报