线段树

线段树,是一种可以实现 \(O(logn)\) 区间修改、\(O(logn)\) 区间查询的算法。线段树存储的是某个区间中的值,利用二分查找的原理实现修改与查询。
已存储区间和为例,区间 \([1, 5]\) 可以分为两个小区间 \([1, 3]\)\([4, 5]\),最后区间 \([1, 5]\) 的值就是这两个小区间的值的和。同样的,区间 \([l, r]\) 可以分为 \([l, mid]\)\([mid + 1, r]\),其中 \(mid = \lfloor \frac{l + r}{2} \rfloor\)。就这样,我们可以利用这个数组快速查找区间和。
在建树、修改和查询中,我们始终都要维护好这棵树,因此我们需要一个 pushup 函数,来维护这棵树的正确性:

void pushup(int u)
{
	w[u] = w[u * 2] + w[u * 2 + 1];
}

为了方便于计算,我们把编号为 \(u\) 的区间分为两个小区间时,把它们的编号分别设为 \(u \times 2, u \times 2 + 1\)
接下来就是建树了。
我们在建立区间 \([l, r]\) 的树时,如果 \(l = r\),则说明这个区间内只有一个数 \(a_l\),因此我们直接把它赋值为 \(a_l\);如果 \(l \neq r\),则我们先计算出 \(mid\),然后分别建树 \([l, mid]\)\([mid + 1, r]\),最后 pushup 一下,维护好这棵树。

void build(int u, int l, int r)
{
	if (l == r)
	{
		w[u] = a[l];
		return;
	}
	int mid = (l + r) >> 1;
	build(u * 2, l, mid);
	build(u * 2 + 1, mid + 1, r);
	pushup(u);
}

接下来,考虑怎么进行区间修改。
用一个懒标记 \(lzy\) 数组来记录这个区间内被打上的标记。每一次修改和查询的时候,就先将这个区间和加上 \(lzy\) 的值乘上区间的长度。打标记和下传标记的代码如下:

void maketag(int u, int len, int x)
{
	w[u] += len * x; //加了len个x
	lzy[u] += x;
}
void pushdown(int u, int l, int r)
{
	int mid = (l + r) >> 1;
	maketag(u * 2, mid - l + 1, lzy[u]);
	maketag(u * 2 + 1, r - mid, lzy[u]);
	lzy[u] = 0; //懒标记清零
}

单点查询:
每次查询时,要保证当前区间内包含查找的数,也就是说,当你需要找第 \(p\) 个元素时,假设你当前找到了区间 \([l, r]\),则先算出 \(mid\),如果 \(p \leq mid\),说明 \(p\) 在它的左子树中,否则在它的右子树中。代码:

int query1(int u, int l, int r, int p)
{
	if (l == r) return w[u]; //找到了
	int mid = (l + r) >> 1;
	pushdown(u, l, r); //先下传标记,不然以前的修改可能没有传下去
	if (p <= mid) return query(u * 2, l, mid, p); //左子树
	else return query(u * 2 + 1, mid + 1, r, p); //右子树
}

单点修改:基本同单点查询,但是修改后要 pushup

void update1(int u, int l, int r, int p, int x)
{
	if (l == r)
	{
		w[u] = x;
		return;
	}
	int mid = (l + r) >> 1;
	pushdown(u, l, r);
	if (p <= mid)
	{
		update1(u * 2, l, mid, p, x);
	}
	else
	{
		update1(u * 2 + 1, mid + 1, r, p, x);
	}
	pushup(u);
}

区间查询与修改:在递归到 \([l, r]\) 的区间时,如果当前区间被包括在要查询的区间内,则直接返回区间值,如果完全无交集,返回 \(0\),如果有部分交集,则计算 \(mid\),再递归处理区间 \([l, mid]\)\([mid + 1, r]\),返回它们的和。修改同理,若被包含,则全部打上标记,若没有交集,则不操作,否则递归处理。

bool inrange(int l, int r, int L, int R)
{
	return (l <= L) && (R <= r);
}
bool outofrange(int l, int r, int L, int R)
{
	return (r < L) || (R < l);
}
void update(int u, int lnow, int rnow, int l, int r, int k)
{
	if (inrange(l, r, lnow, rnow))
	{
		maketag(u, rnow - lnow + 1, k);
	}
	else if (outofrange(l, r, lnow, rnow))
	{
		return;
	}
	else
	{
		int mid = (lnow + rnow) >> 1;
		pushdown(u, lnow, rnow);
		update(u * 2, lnow, mid, l, r, k);
		update(u * 2 + 1, mid + 1, rnow, l, r, k);
		pushup(u);
	}
}
int query(int u, int lnow, int rnow, int l, int r)
{
	if (inrange(l, r, lnow, rnow))
	{
		return w[u];
	}
	else if (outofrange(l, r, lnow, rnow))
	{
		return 0;
	}
	else
	{
		int mid = (lnow + rnow) >> 1;
		pushdown(u, lnow, rnow);
		return query(u * 2, lnow, mid, l, r) + query(u * 2 + 1, mid + 1, rnow, l, r);
	}
}
posted @ 2025-04-28 21:13  langni2013  阅读(28)  评论(0)    收藏  举报