使用二叉索引树进行 RMQ 扩展
Posted
技术标签:
【中文标题】使用二叉索引树进行 RMQ 扩展【英文标题】:Using binary indexed trees for a RMQ extension 【发布时间】:2013-06-17 21:21:48 【问题描述】:RMQ problem 可以这样扩展:
Given 是一个 n
整数数组 A
。
query(x, y):给定两个整数1≤x
,y
≤n
,求A[x], A[x+1], ... A[y]
的最小值;
update(x, v): 给定一个整数 v
和 1 ≤ x
≤ n
做A[x] = v
。
这个问题可以在O(log n)
中使用segment trees 解决这两个操作。
这在纸面上是一种有效的解决方案,但在实践中,段树涉及大量开销,尤其是在递归实现时。
我知道有一种方法可以解决O(log^2 n)
中的一个(或两个,我不确定)操作的问题,使用二叉索引树(可以找到更多资源,但是@ 987654323@ 和 this 分别是 IMO 最简洁和详尽的)。对于适合内存的n
值,此解决方案在实践中更快,因为 BIT 的开销要少得多。
但是,我不知道如何使用 BIT 结构来执行给定的操作。例如,我只知道如何使用它来查询区间和。如何使用它找到最小值?
如果有帮助,我有其他人编写的代码可以满足我的要求,但我无法理解它。这是一段这样的代码:
int que( int l, int r )
int p, q, m = 0;
for( p=r-(r&-r); l<=r; r=p, p-=p&-p )
q = ( p+1 >= l ) ? T[r] : (p=r-1) + 1;
if( a[m] < a[q] )
m = q;
return m;
void upd( int x )
int y, z;
for( y = x; x <= N; x += x & -x )
if( T[x] == y )
z = que( x-(x&-x) + 1, x-1 );
T[x] = (a[z] > a[x]) ? z : x;
else
if( a[ T[x] ] < a[ y ] )
T[x] = y;
在上面的代码中,T
初始化为 0,a
是给定的数组,N
是它的大小(无论出于何种原因,它们都会从 1 开始索引),每次读取都会首先调用 upd
价值。在upd
被调用之前a[x] = v
被执行。
此外,p & -p
与某些 BIT 源中的 p ^ (p & (p - 1))
相同,索引从 1 开始,零元素初始化为无穷大。
谁能解释一下上面的工作原理或我如何用 BIT 解决给定的问题?
【问题讨论】:
for( y = x; x <= N; x += x & -x )
这很深。顺便说一句,T[]
是什么? BTW2:我不确定这是否应该是for( y = x; x < N; x += x & -x )
BTW3:什么是N
?
@wildplasser - 第一个 for
实际上对于 BIT 来说是相当标准的。 T
似乎是实际保存 BIT 信息的位置。至于N
,也就是我的问题陈述中的n
,我会编辑进去。
【参考方案1】:
我没有仔细看代码,但似乎与下面的方案大致一致:
1) 保留BIT的结构,即在数组上强加一个基于2的幂的树结构。
2) 在树的每个节点处,保持在该节点的任何后代中找到的最小值。
3) 给定一个任意范围,将指针放在范围的开始和结束处,并将它们向上移动直到它们相遇。如果您将一个指针向上移动并朝向另一个指针,那么您刚刚进入了一个节点,其中每个后代都是该范围的成员,因此请注意该节点处的该值。如果将指针向上移动并远离另一个指针,则刚刚加入的节点记录了从包括范围外的值中派生的最小值,并且您已经注意到范围内该节点下方的每个相关值,因此忽略该节点的值。
4) 一旦两个指针是同一个指针,范围内的最小值就是你注意到的任何节点中的最小值。
【讨论】:
【参考方案2】:从上面的位摆弄,这就是我们所拥有的:
整数数据数组a
的普通位数组g
存储范围和。
g[k] = sum i = D(k) + 1 .. k a[i]
其中D(k)
只是k
,最低1 位设置为0。这里我们改为
T[k] = min i = D(k) + 1 .. k a[i]
查询的工作方式与普通的 BIT 范围求和查询完全一样,只是在查询进行时取子范围的最小值而不是求和。对于a
中的 N 项,N 中有上限(log N)位,它决定了运行时间。
更新需要更多工作,因为 O(log N) 子范围最小值 - 即 g
的元素 - 受到更改的影响,并且每个都需要一个 O(log N) 查询来解决。这使得整体更新 O(log^2 n)。
在位摆弄级别,这是非常聪明的代码。语句x += x & -x
清除x
中最低顺序的连续1 字符串,然后将下一个最高顺序零设置为1。这正是您“遍历”原始整数x
的BIT 所需要的。
【讨论】:
【参考方案3】:分段树在实践中也是一种有效的解决方案。但是,您不会将它们实现为树。将n
舍入到下一个2 的幂,并使用大小为2*n
的数组rmq
。 rmq
的最后一个 n
条目是 A
。如果j < n
,那么rmq[j] = min(rmq[2*j], rmq[2*j+1])
。您只需要以对数方式查看 rmq
的多个条目即可回答范围最小查询。当A
的条目被更新时,您只需要以对数方式更新rmq
的多个条目。
不过,我不明白你的代码,所以我不会评论它。
【讨论】:
当我说它们在实践中很慢时,这就是我所指的实现。 BIT 仍然对缓存更加友好,并且在实践中会更快。 @IVlad:在此处预取帮助。此外,您不需要使用 radix-2 树。您可以使用基数 B 树并调整 B 以适合您的缓存层次结构。 (也许 B=16 是合适的;它为您提供了 1/4 高度的树,而对于int
s,您每级只查看一个缓存行。)
@IVlad:此外,BIT 解决了一个更简单的问题。它们为您提供前缀和(或产品或分钟),并且只有在您有取消法的情况下,您才能使用它们进行范围查询。我从未见过使用 BITs 代替分段树进行范围查询,但我也从未真正看过。以上是关于使用二叉索引树进行 RMQ 扩展的主要内容,如果未能解决你的问题,请参考以下文章