「学习笔记」线段树

一.线段树的概念

线段树是一种二叉搜索树,每个节点最多有两颗子树,通常称为左、右子树。

这个数据结构将一个区间分成若干个区间,每一个节点存储一个区间 \([L,R]\) 的信息,叶子结点的 \(L\) 等于 \(R\)。

也就是说,我们将 \([1,n]\) 平均分为两个区间,然后一直执行这个操作,分到无法再分为止。

线段树的思想和分治很像:将大区间的信息分为若干个小区间的信息进行操作。

二.线段树的搭建

首先我们要有一个结构体封装节点信息:

struct SegMentTree {
	int l;//区间左端点。
	int r;//区间右端点。
	int sum;//区间和。
	int siz;//区间长度。
	//Do other things.
}S[N];

我们有一个数组 \([1,2,3,4,5]\),线段树的搭建如下。

「学习笔记」线段树

可以发现,对于一个节点 \(u\),它的左子节点和右子节点的编号分别为 \(2u\) 和 \(2u+1\),这样我们就能从根节点一直递归下去建树。

显然,节点 \(u\) 的区间和为它的左子节点和右子节点的区间和之和。

这样,就可以写出建树代码了:

#define ls (x) (x * 2)//左子节点。
#define rs (x) (x * 2 + 1)//右子节点。

void build (int nd, int l, int r) {
	s[nd].l = l, s[nd].r = r, s[nd].siz = r - l + 1;
	//记录信息。
	if (l == r) {
		s[nd].sum = a[l];//a数组为最初给定的数列。
		
		return ;
	}
	
	int mid = l + r >> 1;
	
	build (ls(nd), l, mid);
	build (rs(nd), mid + 1, r);
	//递归左子树和右子树。
	
	s[nd].sum = s[ls(nd)].sum + s[rs(nd)].sum;
	//合并信息。
}

三.线段树的查询

其实单点查询没什么必要学,区间查询可以实现单点查询。

那么这里就只讲解区间查询。

对于一个区间 \([L,R]\),我们要查询它的区间之和。

同样需要递归记录和。

考虑一个节点 \(u\), 设它的左端点为 \(u_l\),右端点为 \(u_l\),询问区间为 \([L,R]\)。

如果 \(L\le u_l\) 并且 \(R \ge u_r\),那么直接返回当前节点的区间和即可。

也就是说当当前节点是询问范围的子集时,就直接返回当前节点的区间和。

如果不满足上述条件,那么就需要递归两个子树,通过合并两个区间的信息获得该区间的答案。

这样很容易得出代码了:

ll query (int nd, int l, int r) {
	if (l <= s[nd].l && r >= s[nd].r) {
		return s[nd].sum;
		//当前节点是询问范围的子集时,就直接返回当前节点的区间和。
	}	
	
	int mid = s[nd].l + s[nd].r >> 1;
	ll res = 0;
	
	if (l <= mid) {
		res += query (ls(nd), l, r);
		//左子树与询问区间有交集,那么递归左子树。
	}
	
	if (r > mid) {
		res += query (rs(nd), l, r);
		//右子树与询问区间有交集,那么递归右子树。
	}
	
	return res;
}

当然,如果需要单点查询节点 \(u\),那么直接将询问区间左右端点都设为 \(u\) 即可。

四.线段树的修改

上一篇:Log4Net 用法记录


下一篇:Asp.net core 使用log4net作为日志组件,记录日志到本地。