Algorithm 使用二叉索引树进行RMQ扩展

Algorithm 使用二叉索引树进行RMQ扩展,algorithm,optimization,data-structures,Algorithm,Optimization,Data Structures,可以像这样扩展: 给定的是一个由n整数A组成的数组 查询(x,y):给定两个整数1≤ x,y≤ n,找到A[x],A[x+1]。。。A[y] 更新(x,v):给定一个整数v和1≤ x≤ ndoA[x]=v 对于使用的两个操作,可以在O(log n)中解决此问题 这在纸面上是一个有效的解决方案,但在实践中,分段树涉及大量开销,特别是在递归实现的情况下 事实上,我知道有一种方法可以解决O(log^2n)中的一个(或两者,我不确定)操作的问题,使用二元索引树(可以找到更多的资源,但在我看来,和分别是最

可以像这样扩展:

给定的是一个由
n
整数
A
组成的数组

查询(x,y):给定两个整数1≤ <代码>x,
y
≤ <代码>n,找到
A[x],A[x+1]。。。A[y]

更新(x,v):给定一个整数
v
和1≤ <代码>x≤ <代码>ndo
A[x]=v

对于使用的两个操作,可以在
O(log n)
中解决此问题

这在纸面上是一个有效的解决方案,但在实践中,分段树涉及大量开销,特别是在递归实现的情况下

事实上,我知道有一种方法可以解决
O(log^2n)
中的一个(或两者,我不确定)操作的问题,使用二元索引树(可以找到更多的资源,但在我看来,和分别是最简洁和详尽的)。对于适合内存的
n
值,这种解决方案实际上更快,因为位的开销要小得多

但是,我不知道如何使用位结构来执行给定的操作。例如,我只知道如何使用它来查询区间和。我如何使用它来找到最小值

如果有帮助的话,我有其他人编写的代码可以满足我的要求,但我无法理解。下面是一段这样的代码:

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;
}
intque(intl,intr){
int p,q,m=0;
对于(p=r-(r&-r);l=l)T[r]:(p=r-1)+1;
if(a[m]
在上面的代码中,
T
初始化为0,
a
是给定的数组,
N
其大小(无论出于何种原因,它们从1开始进行索引),并且首先为每个读取值调用
upd
。在调用
upd
之前,执行
a[x]=v

另外,
p&-p
与某些位源中的
p^(p&(p-1))
相同,索引从1开始,零元素初始化为无穷大


有人能解释一下上面的工作原理吗?或者我如何用一点时间解决给定的问题?

分段树在实践中也是一种有效的解决方案。但是,您不能将它们实现为树。将
n
四舍五入到2的下一次幂,并使用大小为
2*n
的数组
rmq
rmq
的最后一个
n
条目是
A
。如果
j
,则
rmq[j]=min(rmq[2*j],rmq[2*j+1])
。您只需查看
rmq
的对数多个条目即可回答范围最小查询。当更新
A
的条目时,您只需要对数更新
rmq
的多个条目


不过,我不理解您的代码,因此我不打算对此进行评论。

我没有详细查看代码,但它似乎与以下方案大致一致:

1) 保持位的结构,也就是说,在数组上施加基于二次幂的树结构

2) 在树的每个节点上,保留在该节点的任何子节点上找到的最小值

3) 给定任意范围,将指针放在范围的开始和结束处,并向上移动指针,直到它们相遇。如果向上移动一个指针并指向另一个指针,那么您刚刚进入了一个节点,其中每个子体都是该范围的成员,因此请注意该节点上的值。如果将一个指针向上移动并远离另一个指针,则刚加入的节点会记录从值(包括范围外的值)派生的最小值,并且您已经注意到范围内该节点下的每个相关值,因此忽略该节点上的值


4) 一旦两个指针是同一个指针,范围中的最小值就是您注意到的任何节点中的最小值。

从高于位摆弄的级别来看,这就是我们所拥有的:

整数数据数组
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]
该查询的工作方式与普通的位范围和查询完全相同,只是在查询进行时取子范围的最小值,而不是求和。对于
a
中的N个项目,N中有上限(logn)位,它决定了运行时间

更新需要更多的工作,因为O(logn)子范围极小值(即
g
)的元素受更改影响,并且每个元素本身都需要一个O(logn)查询来解决。这使得更新总体上是O(日志^2 n)


这是一个非常聪明的代码。语句
x+=x&-x
清除
x
中最低顺序的连续字符串1,然后将下一个最高顺序的0设置为1。这正是“遍历”原始整数的位所需的
x

用于(y=x;x@wildplasser-for
的第一个
实际上对于BIT来说是相当标准的。
T
似乎是位信息的实际存放位置。至于
N
,也就是我的问题陈述中的
N
,我将在中对其进行编辑。这就是我所指的实现,当时我说它们在实践中很慢。BIT是还有更多的缓存友好性,在实践中会更快。@IVlad:预取帮助。此外,您不需要使用基数-2树。您可以使用基数-B树并调整B以适应您的缓存层次结构。(可能B=16是合适的;它给您一个高度为1/4的树,并且对于
int
s,每个级别只查看一条缓存线。)@IVlad:另外,BITs解决了一个更容易的问题。它们给你前缀和(或乘积或分钟),并且只在你