线段树中的懒惰传播

2025年03月17日 | 阅读 9 分钟

在上篇文章中,我们介绍了线段树及其范围求和问题的示例。我们使用相同的“指定范围求和”问题解释了懒惰传播。

Lazy Propagation in Segment Tree

简单的线段树更新函数是如何工作的?

在上一节中,update 方法用于仅更改数组中的一个值。由于一个数组元素可能位于多个线段树节点的范围内,因此需要注意,数组中单个值的更新可能会导致线段树中出现多个更新。

本文中的简单推理如下所示。

1) 从线段树的根节点开始。

2) 如果需要修改的数组索引超出了当前节点的范围,则返回。

3) 更新当前节点,如果不是,则对子节点重复此操作。

此处显示了上一篇文章中的代码。

C++ 代码

上述策略的应用如下所示:

如果许多数组索引需要更新怎么办?

例如,将索引在 2 到 7 之间的每个数组元素增加 10。对于从 2 到 7 的每个索引,必须调用上述更新。通过创建一个名为 updateRange() 的函数来根据需要更新节点,我们可以避免进行多次调用。

C++ 代码

上述策略的应用如下所示:

懒惰传播是一种用于加快范围更新速度的优化技术。

当存在多个更新且正在对范围执行更新时(避免在更新中进行递归调用),我们可以延迟一些更新,并在需要时才执行这些更新。

请注意,线段树中的一个节点存储或表示多个索引的查询结果。此外,如果更新操作的范围包含该节点,则其所有后代也必须更新。例如,考虑上图中值为 27 的节点。此节点包含索引 3 到 5 之间的值的总和。如果我们的更新查询涵盖范围 2 到 5,我们必须同时更新此节点及其所有后代。

通过将此更新信息存储在称为懒惰节点或值的独立节点中,我们可以仅更新值为 27 的节点,并延迟对其后代的更新。我们创建了一个名为 lazy[] 的数组来代表懒惰节点。lazy[] 的大小与用于表示线段树的代码中的数组相同,即 tree[]。

目标是将 lazy[] 的所有元素设置为 0。如果 lazy[i] 的值为 0,则表示线段树节点 i 上没有待处理的更改。lazy[i] 的非零值表示在对线段树节点 i 进行任何查询之前,必须将此总和添加到该节点。

这是更新后的更新技术。

查询函数是否也已更改?

如果查询发送到一个尚未更新的节点,可能会出现问题,因为我们修改了 update 以推迟其活动。因此,我们还需要修改上一篇文章中使用的查询方法 getSumUtil。getSumUtil() 现在在更新节点之前确定是否有待处理的更新。一旦它确认所有待处理的更新都已完成,它的功能就与之前的 getSumUtil() 类似。

下面的程序展示了懒惰传播的工作原理。

C++ 代码

上述策略的应用如下所示:

输出

Sum of values in given range = 15
Updated sum of values in given range = 45 

时间复杂度:O(n)

辅助空间: O(MAX)