如果更新比简单的加法或乘法更复杂,如何应用惰性方法来更新线段树?
How is it possible to apply the lazy approach to update a segment tree if the update is more complex than simple addition or multiplication?
考虑this question. In this segment tree solution, we are updating all nodes of the tree in the given range. Is it possible to apply lazy propagation这个问题?
编辑:考虑在每个更新操作中 arr[i] = (1-(1-arr[i])*a)
,其中 L<=i<=R
和 a
是常数。
是的,确实有可能,至少在某些情况下是这样。
基本上,您需要一种有效存储惰性操作的方法,以及一种有效地将两个存储的惰性操作合并为一个的方法。
比如更新操作是段赋值,即a[l] = x
、a[l+1] = x
、...
、a[r-1] = x
、a[r] = x
。
这个对整个子树的操作可以只存储为值 x
,这意味着操作是将 x
分配给这个子树的每个顶点。
对于顶点 v
中的惰性传播,我们只需将其应用于顶点 v
的直接子节点,并在那里存储相同的惰性操作 x
。
请注意,子代中任何旧的惰性操作都只是被赋值擦除。
这就是赋值的本质。
关于您添加的示例,操作 arr[i] = (1 - (1 - arr[i]) * a)
,让我们看看在使用常量 a
和 b
.
进行两次此类操作后值如何变化
运算前,设值为v
。
第一个之后就变成了w = 1 - (1 - v) * a
,也就是a*v + (1-a)*1
.
第二次运算后,数值变成1 - (1 - w) * b
,也就是b*w + (1-b)*1
,再变成b*a*v + b*(1-a)*1 + (1-b)*1
,最后变成(b*a)*v + (1-b*a)*1
。
(我可能混淆了 +s 和 -s,但希望这不会改变整个画面。)
我们现在可以看出该值是原始值的线性函数,因此我们可以分别存储线性项和常数项的系数b*a
和1-b*a
。
现在的问题是系数可能增长得太快,很快就会超过存储类型的容量(int
、double
或其他)。
现在,如果问题处理的是整数残基对某个整数取模而不仅仅是整数或实数,那不是问题;否则,存储系数很快就会出现问题。
我假设您的查询操作是在 [L, R]
.
范围内查找总和
您当然希望高效地执行此操作,可能每个操作 O(log n)
。
您需要一种在 lazy
字段中存储数据的方法,该方法允许您在遍历查询树时计算更新。
让我们看看是否可以用更好的方式编写更新:
v = 1 - (1 - v) * a
= 1 - (a - av)
= 1 - a + av
如果我们这样做两次:
1 - a + av = 1 - (1 - [1 - a + av]) * a
= 1 - (a - a + a**2 - a**2 v)
= 1 - a + a - a**2 + a**2 v
= 1 - a**2 + a**2 v
相当于(应用于整个范围):
- 乘以
a
- 减去
a
- 添加
1
更新惰性字段时,很明显你只是增加a
的指数。
您可以按照您 link 阅读的懒惰传播文章中的描述懒惰地执行所有这 3 项操作。
因此您的更新操作可以分为 3 个惰性更新,每个在 O(log n)
时间内完成,总时间为 O(log n)
。
考虑this question. In this segment tree solution, we are updating all nodes of the tree in the given range. Is it possible to apply lazy propagation这个问题?
编辑:考虑在每个更新操作中 arr[i] = (1-(1-arr[i])*a)
,其中 L<=i<=R
和 a
是常数。
是的,确实有可能,至少在某些情况下是这样。
基本上,您需要一种有效存储惰性操作的方法,以及一种有效地将两个存储的惰性操作合并为一个的方法。
比如更新操作是段赋值,即a[l] = x
、a[l+1] = x
、...
、a[r-1] = x
、a[r] = x
。
这个对整个子树的操作可以只存储为值 x
,这意味着操作是将 x
分配给这个子树的每个顶点。
对于顶点 v
中的惰性传播,我们只需将其应用于顶点 v
的直接子节点,并在那里存储相同的惰性操作 x
。
请注意,子代中任何旧的惰性操作都只是被赋值擦除。
这就是赋值的本质。
关于您添加的示例,操作 arr[i] = (1 - (1 - arr[i]) * a)
,让我们看看在使用常量 a
和 b
.
运算前,设值为v
。
第一个之后就变成了w = 1 - (1 - v) * a
,也就是a*v + (1-a)*1
.
第二次运算后,数值变成1 - (1 - w) * b
,也就是b*w + (1-b)*1
,再变成b*a*v + b*(1-a)*1 + (1-b)*1
,最后变成(b*a)*v + (1-b*a)*1
。
(我可能混淆了 +s 和 -s,但希望这不会改变整个画面。)
我们现在可以看出该值是原始值的线性函数,因此我们可以分别存储线性项和常数项的系数b*a
和1-b*a
。
现在的问题是系数可能增长得太快,很快就会超过存储类型的容量(int
、double
或其他)。
现在,如果问题处理的是整数残基对某个整数取模而不仅仅是整数或实数,那不是问题;否则,存储系数很快就会出现问题。
我假设您的查询操作是在 [L, R]
.
您当然希望高效地执行此操作,可能每个操作 O(log n)
。
您需要一种在 lazy
字段中存储数据的方法,该方法允许您在遍历查询树时计算更新。
让我们看看是否可以用更好的方式编写更新:
v = 1 - (1 - v) * a
= 1 - (a - av)
= 1 - a + av
如果我们这样做两次:
1 - a + av = 1 - (1 - [1 - a + av]) * a
= 1 - (a - a + a**2 - a**2 v)
= 1 - a + a - a**2 + a**2 v
= 1 - a**2 + a**2 v
相当于(应用于整个范围):
- 乘以
a
- 减去
a
- 添加
1
更新惰性字段时,很明显你只是增加a
的指数。
您可以按照您 link 阅读的懒惰传播文章中的描述懒惰地执行所有这 3 项操作。
因此您的更新操作可以分为 3 个惰性更新,每个在 O(log n)
时间内完成,总时间为 O(log n)
。