算法学习笔记(43): 可持久化线段树 - 区间加!

可持久化线段树

也叫做主席树,单点修改时简单,使用空间 \(O(\log n)\),问题在于如何区间加。

区间覆盖可以做,但是似乎只能单点查?

所以我们需要引入标记永久化的概念。

一个标记在没有下放前会放在 \(O(\log n)\) 个点上,这覆盖了整个操作区间。

一般来说我们会 update 更新区间 sum 标记,但是在这里,由于可持久化了,没有办法向上更新,所以我们需要在经过的每一个区间更新操作对这个区间的影响,或者说是对区间和的影响。

这只顾及了上方,还要考虑下方。

对于下方的区间我们都会有操作带来的影响,所以需要一个额外的标记记录对于下方区间的影响。

using lint = long long;
// 这里使用的是左闭右开的写法!
void add(int L, int R, lint v, int p, int l, int r) {
	if (L <= l && r <= R) {
		return (void)(laz[p] += v);
	} int mid = (l + r) >> 1;
	sum[p] += (min(R, r) - max(L, l)) * v;
	if (L < mid) add(L, R, v, lc(p), l, mid);
	if (R > mid) add(L, R, v, rc(p), mid, r);
}

在查询的时候,我们只需要在原本 sum 的基础上加上 laz 对于查询区间的影响即可。

值得一提的是,laz 标记必须限制在其所对应的区间呢,它并不一定会影响整个查询的区间!

lint query(int L, int R, int p, int l, int r) {
	if (L <= l && r <= R) {
		return sum[p] + (r - l) * val[p];
	} lint mid = (l + r) >> 1, res = (min(R, r) - max(L, l)) * val[p];
	if (L < mid) res += query(L, R, lc(p), l, mid);
	if (R > mid) res += query(L, R, rc(p), mid, r);
	return res;
}

至于可持久化,我们需要改的东西也很少:

int clone(int p) { // 这是唯一需要多增的一个函数
	laz[++use] = laz[p], sum[use] = sum[p];
	lc[use] = lc[p], rc[use] = rc[p];
	return use;
}

void add(int L, int R, lint v, int &p, int l, int r) {
	p = clone(p); // 这是唯二需要多增的一行
	if (L <= l && r <= R) {
		return (void)(laz[p] += v);
	} int mid = (l + r) >> 1;
	sum[p] += (min(R, r) - max(L, l)) * v;
	if (L < mid) add(L, R, v, lc[p], l, mid);
	if (R > mid) add(L, R, v, rc[p], mid, r);
}

lint query(int L, int R, int p, int l, int r) {
	if (!p) return 0; // 其实这个特判不要也罢
	if (L <= l && r <= R) {
		return sum[p] + (r - l) * val[p];
	} lint mid = (l + r) >> 1, res = (min(R, r) - max(L, l)) * val[p];
	if (L < mid) res += query(L, R, lc[p], l, mid);
	if (R > mid) res += query(L, R, rc[p], mid, r);
	return res;
}

接下来就是喜闻乐见的复杂度分析了,时间复杂度是 \(O(\log n)\) 的准没错,但是空间复杂度呢?

乍一眼看上去应该是 \(O(\log^2 n)\) 的,因为会影响 \(O(\log n)\) 个结点,每个结点上方有 \(O(\log n)\) 个结点。

但是实际上还是 \(O(\log n)\) 的,只是带了一个 \(4\) 的常数。

因为线段树操作区间是连续的,这使得每一层至多有两个额外的区间没有被操作,而原本我们影响到的区间有 \(2 \log n\) 个,所以总共会影响 \(4 \log n\) 个结点。

为什么有两个?因为只能是最左边和最右边的两个部分才不可以被完全覆盖,中间的是一定被覆盖了的!

posted @ 2023-11-25 08:18  jeefy  阅读(59)  评论(0编辑  收藏  举报