树上差分

本文介绍了如何在树结构中使用树上差分技术,通过倍增法计算两个节点的最近公共祖先(LCA),并提供了相应的递归代码示例。这种方法在处理节点深度和祖先关系时具有O(logN)的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

树上差分

问题引入

当我们要对某条路径进行 + + + − - 操作时,我们应该怎么做?
比如说 N o d e 1 Node1 Node1 N o d e 2 Node2 Node2 的路径,先分为 N o d e 1 Node1 Node1 到 LCA 和 LCA 到 N o d e 2 Node2 Node2 两条链。
如图:

然后,我们开一个 T a g Tag Tag 数组打标记。
我们让 T a g [ N o d e 1 ] + + Tag[Node1]++ Tag[Node1]++ T a g [ N o d e 2 ] + + Tag[Node2]++ Tag[Node2]++ T a g [ L C A ] − − Tag[LCA]-- Tag[LCA] T a g [ F a [ L C A ] ] − − Tag[Fa[LCA]]-- Tag[Fa[LCA]]
此时,我们会发现,每个点的权值变化相当于它的子树的标记和!
这就是树上差分。
注意:这仅适用于多次修改后在进行查询,如果中途有多次查询是无法解决的。

LCA

方法

使用倍增。
F a [ N o d e ] [ i ] Fa[Node][i] Fa[Node][i] 表示点 N o d e Node Node 2 i 2^i 2i 级祖先, F a [ N o d e ] [ 0 ] Fa[Node][0] Fa[Node][0] 就是它的父节点。
那么对于 i ⩾ 1 i \geqslant 1 i1,我们有转移式: F a [ N o d e ] [ i ] = F a [ F a [ N o d e ] [ i − 1 ] ] [ i − 1 ] Fa[Node][i]=Fa[Fa[Node][i-1]][i-1] Fa[Node][i]=Fa[Fa[Node][i1]][i1]
也就是说,点 N o d e Node Node 2 i 2^i 2i 级祖先就是点 N o d e Node Node 2 i − 1 2^{i-1} 2i1 级祖先 的 2 i − 1 2^{i-1} 2i1 级祖先。( 2 i − 1 + 2 i − 1 = 2 i − 1 ∗ 2 = 2 i 2^{i-1}+2^{i-1}=2^{i-1}*2=2^i 2i1+2i1=2i12=2i
对于树上的每个点我们都能处理 F a Fa Fa 的值。
N o d e 1 Node1 Node1 N o d e 2 Node2 Node2 的 LCA 时,假设深度 D e p [ N o d e 1 ] > D e p [ N o d e 2 ] Dep[Node1] > Dep[Node2] Dep[Node1]>Dep[Node2],我们先计算出深度差 D i f = D e p [ N o d e 1 ] − D e p [ N o d e 2 ] Dif = Dep[Node1] - Dep[Node2] Dif=Dep[Node1]Dep[Node2],然后把 D e p Dep Dep 转成二进制,通过倍增跳跃使得点 N o d e 1 Node1 Node1 和点 N o d e 2 Node2 Node2 深度相同(一定要从高位往低位跳)。
然后,若点 N o d e 1 Node1 Node1 和点 N o d e 2 Node2 Node2 相同,则 N o d e 1 Node1 Node1 (或 N o d e 2 Node2 Node2) 就是 LCA。
否则,我们先考虑 2 20 2^{20} 220 2 2 2 的某个次幂,只要满足 > N > N >N 就行),如果 N o d e 1 Node1 Node1 N o d e 2 Node2 Node2 同时跳 2 20 2^{20} 220 级祖先仍然不同,那么就跳,否则不动;接着枚举 2 19 2^{19} 219,以此类推。(注意不能越界)
到最后, N o d e 1 Node1 Node1 N o d e 2 Node2 Node2 只需要再跳一步就是 LCA 了。时间复杂度是 Θ ( log ⁡ N ) \Theta(\log N) Θ(logN) 的。
倍增原理是不难的,但是应用是非常广泛的,大家需要练就的是用倍增解决具体问题的能力!!

代码(LCA)

void DFS(int Current, int Previous) {
	//	std::cout << "DFS\n";
	Tree[Current][0] = Previous;
	for (int i = 1; i <= Log2[Dep[Current]]; i++) {
		Tree[Current][i] = Tree[Tree[Current][i - 1]][i - 1];
	}
	for (int Node2 : Gr[Current]) {
		Dep[Node2] = Dep[Current] + 1;
		DFS(Node2, Current);
	}
}
int LCA(int A, int B) {
	//std::cout << "LCA\n";
	if (Dep[A] < Dep[B]) std::swap(A, B);
	while (Dep[A] > Dep[B]) A = Tree[A][Log2[Dep[A] - Dep[B]]];
	if (A == B) return A;
	for (int i = Log2[Dep[A]]; i >= 0; i--) {
		if(Tree[A][i] != Tree[B][i]) {
			A = Tree[A][i];
			B = Tree[B][i];
		}
	}
	return Tree[A][0];
}

习题

P2680
CF555E
CF702E
1967
P4281
CF1175E

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值