Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/algorithm/10.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Algorithm 需要一个明确的解释范围更新和范围查询二进制索引树_Algorithm_Data Structures_Fenwick Tree - Fatal编程技术网

Algorithm 需要一个明确的解释范围更新和范围查询二进制索引树

Algorithm 需要一个明确的解释范围更新和范围查询二进制索引树,algorithm,data-structures,fenwick-tree,Algorithm,Data Structures,Fenwick Tree,我已经阅读了一些关于范围更新的教程——二叉索引树的范围查询。我一个也听不懂。我不明白再建一棵树的必要性 有人能用简单的英语给我解释一下吗?让我试着解释一下 为什么我们需要第二棵树?我不能回答这个问题。严格地说,我不能证明仅仅使用一个二叉索引树是不可能解决这个问题的(我从来没有在任何地方见过这样的证明) 怎么能想出这个办法呢?再说一遍,我不知道。我不是这个算法的发明者。所以我不知道为什么它看起来像这样。我将试图解释的唯一一件事是为什么以及如何使用这种方法 为了更好地理解这个算法,我们首先应该忘记二

我已经阅读了一些关于范围更新的教程——二叉索引树的范围查询。我一个也听不懂。我不明白再建一棵树的必要性


有人能用简单的英语给我解释一下吗?让我试着解释一下

  • 为什么我们需要第二棵树?我不能回答这个问题。严格地说,我不能证明仅仅使用一个二叉索引树是不可能解决这个问题的(我从来没有在任何地方见过这样的证明)

  • 怎么能想出这个办法呢?再说一遍,我不知道。我不是这个算法的发明者。所以我不知道为什么它看起来像这样。我将试图解释的唯一一件事是为什么以及如何使用这种方法

  • 为了更好地理解这个算法,我们首先应该忘记二叉索引树本身是如何工作的。让我们将其视为支持两个操作的黑盒:更新一个元素并在
    O(logn)
    time中执行范围和查询。我们只想使用一个或多个这样的“黑匣子”来构建一个能够高效执行范围更新和查询的数据结构

  • 我们将维护两个二元索引树:
    T1
    T2
    。我将使用以下符号:
    T.add(pos,delta)
    用于通过
    delta
    值在位置
    pos
    执行点更新,而
    T.get(pos)
    用于求和
    [0…pos]
    。我声明,如果更新函数如下所示:

    void update(left, right, delta)
        T1.add(left, delta)
        T1.add(right + 1, -delta);
        T2.add(left, delta * (left - 1))
        T2.add(right + 1, -delta * right);
    
    范围查询的回答是这样的(对于前缀
    [0…pos]
    ):

    那么结果总是正确的

  • 为了证明其正确性,我将证明以下语句:每次更新都会适当地更改答案(它通过归纳法为所有操作提供证明,因为最初所有操作都用零填充,正确性是显而易见的)。假设我们有一个
    左、右、DELTA
    更新,现在我们正在执行
    pos
    查询(即,0…pos sum)。让我们考虑3种情况:
    i)
    pos
    。更新不会影响此查询。答案是正确的(由于归纳假设)。

    ii)
    L试图以更直观的方式(我理解的方式)解释。我将把它分为四个步骤:

    假设更新是在A和B之间,并带有V,并且查询是一个前缀查询,因为任何索引=A都会受到它的影响。然后将V从B+1中删除,因此任何查询X>=B+1都不会看到V添加到A中。这里没有令人惊讶的地方

    为范围更新/点树添加前缀查询
    T1.sum(X)
    是对X处第一棵树的点查询。我们乐观地假设X之前的每个元素都等于X处的值。这就是为什么我们要
    T1.sum(X)*X
    。显然这不太正确,这就是为什么我们:

    使用修改的范围更新/点查询树修复结果(T2) 在更新范围时,我们还更新了第二棵树,以告诉我们需要修复第一个
    T1.sum(X)*X
    查询多少。此更新包括从任何查询X>=A中删除
    (A-1)*V
    。然后我们为X>=B添加
    B*V
    。我们这样做是因为对第一个树的查询不会为X>=B+1返回V(因为
    T1.add(B+1,-V)
    ),所以我们需要以某种方式告诉您,对于任何查询X>=B+1,都有一个矩形区域
    (B-A+1)*V
    。我们已经从A中删除了
    (A-1)*V
    ,我们只需要将
    B*V
    添加回B+1即可

    把它包装在一起
    我花了很多天来理解范围更新,在这里用示例写了简单的解释:

    这是我见过的最好的解释。你能解释一下“我们乐观地假设X之前的每个元素都等于X处的值”这句话吗?举个例子可能会很有帮助!你能解释一下你是如何得到这个
    T2.add(左,delta*(左-1))T2.add(右+1,-delta*右)?@user3739818我是怎么得到它的?好的,构造一个算法是一个创造性的过程,所以一个公平的答案是:你可以用前缀和画一些图片,然后看到它应该是这样的。在你弄明白这一点之后,你可以严格地证明这一点。
    
    int getSum(pos)
        return T1.sum(pos) * pos - T2.sum(pos)
    
    #include <bits/stdc++.h>
    
    using namespace std;
    
    // Binary index tree.
    struct BIT {
      vector<int> f;
    
      BIT(int n = 0) {
        f.assign(n, 0);
      }
    
      int get(int at) {
        int res = 0;
        for (; at >= 0; at = (at & (at + 1)) - 1)
          res += f[at];
        return res;
      }
    
      void upd(int at, int delta) {
        for (; at < f.size(); at = (at | (at + 1)))
          f[at] += delta;
      }
    };
    
    // A tree for range updates and queries.
    struct Tree {
      BIT f1;
      BIT f2;
    
      Tree(int n = 0): f1(n + 1), f2(n + 1) {}
    
      void upd(int low, int high, int delta) {
        f1.upd(low, delta);
        f1.upd(high + 1, -delta);
        f2.upd(low, delta * (low - 1));
        f2.upd(high + 1, -delta * high);
      }
    
      int get(int pos) {
        return f1.get(pos) * pos - f2.get(pos);
      }
    
      int get(int low, int high) {
        return get(high) - (low == 0 ? 0 : get(low - 1));
      }
    };
    
    // A naive implementation.
    struct DummyTree {
      vector<int> a;
    
      DummyTree(int n = 0): a(n) {}
    
      void upd(int low, int high, int delta) {
        for (int i = low; i <= high; i++)
          a[i] += delta;
      }
    
      int get(int low, int high) {
        int res = 0;
        for (int i = low; i <= high; i++)
          res += a[i];
        return res;
      }
    };
    
    int main() {
      ios_base::sync_with_stdio(0);
      int n = 100;
      Tree t1(n);
      DummyTree t2(n);
      for (int i = 0; i < 10000; i++) {
        int l = rand() % n;
        int r = rand() % n;
        int v = rand() % 10;
        if (l > r)
          swap(l, r);
        t1.upd(l, r, v);
        t2.upd(l, r, v);
        for (int low = 0; low < n; low++)
          for (int high = low; high < n; high++)
        assert(t1.get(low, high) == t2.get(low, high));
      }
      return 0;
    }
    
    update(A, B, V):
        T1.add(A, V)         # add V to any X>=A
        T1.add(B+1, -V)      # cancel previously added V from any X>=B+1
    
        T2.add(A, (A-1)*V)   # add a fix for (A-1)s V that didn't exist before A
        T2.add(B+1, -B*V)    # remove the fix, and add more (B-A+1)*V to any query 
                             # X>=B+1. This is really -(A-1)*V -(B-A+1)*V, but it 
                             # simplifies to -B*V
    
    sum(X):
        return T1.sum(X)*X - T2.sum(X)