Point Update Rage Sum

参考:

引入:

给定数组​a,给个​x,y,有一下操作:

  1. ​a[x...y]的和
  2. ​a[x]自增​y

第一种就是【区间求和】,第二种就是【单点修改】。对应range sum还有point update。其实光是区间求和问题,我一个前缀和也能做,但是带上修改的就不行了,比较固定,所以我们需要更高级的一些数据结构来存区间信息。


Feinwick Tree (树状数组)

首先出场的是树状数组,他比线段树要难理解,但是代码简单。

下图就是树状数组的一个例子(来自oi-wiki):

性质

这里​a就是原数组,​c就是树状数组。​c中存的是​a中某段的和,但是具体怎么知道​c[i]对应的是哪段​a[x....y]呢?首先,我们可以发现,​a[x....y]中的​y一定等于​i,例如​c[4]=\sum a[1]...a[4],所以关键是​x,也就是左端点。

对于这个问题,我们先看上图有什么规律:

  • 第一层:​1,3,5,7,也就都是奇数
  • 第二层:​2,6
  • 第三层:​4
  • 第四层:​8

有点规律,但不多。我们可以看到每一层代表了他管辖范围的长度,例如第​3层的​c[4]管辖​a[1...4]。所以第​n层的​c[i]管辖的就是​a[i-n,i]。现在我们的问题变成了:给定​c[i],如何确定他在哪层?

我们需要引入二进制来考虑,其实这也是这个算法比较精巧的点,再列举一下:

  • 第一层
    • ​(1)_2=1
    • ​(3)_2=11
    • ​(5)_2=101
    • ​(7)_2=111
    • 他们都是奇数,结尾都是​1
  • 第二层
    • ​(2)_2=10
    • ​(6)_2=1010
    • 结尾都是​10
  • 第三层
    • ​(4)_2=100
    • 结尾是​100,数组长度有点小但如果边长点后面的第三层也是结尾​100,这里就不举例了
  • 第四层
    • ​(8)_2=1000
    • 结尾是​1000

这下看懂了,给定c[i],他的层数就是他二进制截取最后一个​1和后面的​0 我们可以使用lowbit来计算:

lowbit

lowbit(x)的意思就是二进制截取最后一个​1和后面的​0。根据位运算知识,lowbit(x)=x&(-x),具体原因不细说了温习一下位运算就好。

代码:

int lowbit(int x) {
  // x 的二进制中,最低位的 1 以及后面所有 0 组成的数。
  // lowbit(0b01011000) == 0b00001000
  //          ~~~~^~~~
  // lowbit(0b01110010) == 0b00000010
  //          ~~~~~~^~
  return x & -x;
}

注意 lowbit截取的不是最后一个​1的位置,而是​1000....这个整体组成的2。

得出结论:​c[x]=\sum^x_{i=x-lowbit(x)+1}a[i],这就是树状数组每个元素存的。

查询

问题:给定​x,y,求​a[x...y]的和。如果他问的是​a[1...8],那么答案很简单就是​c[8]。但是如果是​a[2...8]那么还要给他这个区间段拆分为​c[8]-a[1],换个例子问​a[2...7],而我们在前缀和里知道:​a[2..7]=prefix[7]-prefix[1],所以我们只要有一个函数能处理​a[1...x]就可以了,然后和前缀和一样剪掉多算的​prefix[1]

所以思路就是给定查询区间​a[1...y],从 i=y 开始往左跳,每次 i=i-lowbit(i),然后在 i=0时终止。

代码如下:

int sum(int l, int r) {
    int i = r;
    int acc = 0;
    for (; i > 0; i = i - lowbit(i)) {
        acc += f[i];
    }
}
查询时间复杂度证明

(来自 oi-wiki

考虑 ​r​l 不同的最高位,一定有 ​r 在这一位上为 ​1​l 在这一位上为 ​0(因为 ​r \geq l)。

如果 ​r 在这一位的后面仍然有 ​1,一定有 ​r - \text{lowbit}(r) \geq l,所以下一步一定是把 ​r 的最低位 ​1 填为 ​0;如果 ​r 的这一位 ​1 就是 ​r 的最低位 ​1,无论是 ​r \gets r - \text{lowbit}(r) 还是 ​r \gets r - 1​r 的这一位 ​1 一定会变为 ​0

因此,经过至多 ​\log n 次变换后,​r​l 不同的最高位一定可以下降一位。所以,总时间复杂度是 ​\Theta(\log^2 n)

单点修改

单点修改的思路,就是区间查询倒过来。

给定几种更新更新,其中都可以通过一些变形成为第一种:

  1. ​a[x]=a[x]+k
  2. ​a[x]=k\Rightarrow a[x]=a[x]+(p-a[x]),k=(p-a[x])
  3. ​a[x]=a[x]\times p\Rightarrow a[x]=a[x]+(a[x]\times p-a[x]),k=(a[x]\times p-a[x])

所以只需要考虑第一种的代码怎么写。其实就是当​a[x]=a[x]+k的时候,管辖​a[x]​c需要更新。而管辖​a[x]的只有​c[x+n\times lowbit(x)]

代码:

void update(int x, int k) {
    for (int i = x; i <= n; i += lowbit(i)) {
        c[i] += k;
    }
}
修改时间复杂度证明

(来自 oi-wiki

对于单点修改操作:跳父亲时,访问到的高度一直严格增加,且始终有 ​x \leq n。由于点 ​x 的高度是 ​\log_2 \text{lowbit}(x),所以跳到的高度不会超过 ​\log_2 n,所以访问到的 ​c 的数量是 ​\log n 级别。

因此,单次单点修改复杂度是 ​\Theta(\log n)

建树

建树的操作可以被转化为​n单点修改,复杂度为​O(n\log n),就不写代码了。

完整模板

// Template-based Fenwick Tree class
template <typename T>
class FenwickTree {
private:
    vector<T> c; // Fenwick Tree array
    int n;       // Size of the array

    // Function to calculate the lowest bit (lowbit)
    // lowbit(x) = the lowest set bit of x and the trailing 0s
    int lowbit(int x) {
        return x & -x;
    }

public:
    // Constructor: initializes the Fenwick Tree with size `size`
    FenwickTree(int size) {
        n = size;
        c.resize(n + 1, 0); // Fenwick Tree indices start from 1
    }

    // Point update: add `k` to position `x`
    void update(int x, T k) {
        for (int i = x; i <= n; i += lowbit(i)) {
            c[i] += k;
        }
    }

    // Prefix sum query: calculate the sum of elements a[1] to a[x]
    T query(int x) {
        T sum = 0;
        for (int i = x; i > 0; i -= lowbit(i)) {
            sum += c[i];
        }
        return sum;
    }

    // Range sum query: calculate the sum of elements a[l] to a[r]
    T rangeQuery(int l, int r) {
        return query(r) - query(l - 1);
    }

    // Build the Fenwick Tree from an input array
    void build(const vector<T>& a) {
        for (int i = 1; i <= n; ++i) {
            update(i, a[i - 1]); // Convert 0-based index of `a` to 1-based index
        }
    }

    // Debugging function to print the Fenwick Tree array
    void printTree() {
        for (int i = 1; i <= n; ++i) {
            cout << "c[" << i << "] = " << c[i] << endl;
        }
    }
};

使用例:
```cpp
int main() {
    // Example: input array
    vector<int> a = {1, 2, 3, 4, 5};
    int n = a.size();

    // Initialize Fenwick Tree with the size of the array
    FenwickTree<int> fenwick(n);

    // Build the Fenwick Tree from the input array
    fenwick.build(a);

    // Query the sum of a range
    cout << "Sum of range [2, 4]: " << fenwick.rangeQuery(2, 4) << endl; // Output: 9

    // Perform a point update
    fenwick.update(3, 5); // Add 5 to a[3]

    // Query the range sum again after the update
    cout << "Sum of range [2, 4] after update: " << fenwick.rangeQuery(2, 4) << endl; // Output: 14

    return 0;
}
模版题1:P3374 【模板】树状数组 1 - 洛谷

代码:

/*
 * Created by: Chixiyu
 * Date: 2025-02-19
 */
#include <bits/stdc++.h>
using namespace std;

template <typename T> class FenwickTree {
  private:
    vector<T> c;
    int n;

    // Function to calculate the lowest bit (lowbit)
    int lowbit(int x) { return x & -x; }

  public:
    // Constructor: initializes the Fenwick Tree with size `size`
    FenwickTree(int size) {
        n = size;
        c.resize(n + 1, 0);
    }

    // Point update: add `k` to position `x`
    void update(int x, T k) {
        for (int i = x; i <= n; i += lowbit(i)) {
            c[i] += k;
        }
    }

    // Prefix sum query: calculate the sum of elements a[1] to a[x]
    T query(int x) {
        T sum = 0;
        for (int i = x; i > 0; i -= lowbit(i)) {
            sum += c[i];
        }
        return sum;
    }

    // Range sum query: calculate the sum of elements a[l] to a[r]
    T rangeQuery(int l, int r) { return query(r) - query(l - 1); }

    // Build the Fenwick Tree from an input array
    void build(const vector<T> &a) {
        for (int i = 1; i <= n; ++i) {
            update(i, a[i - 1]);
        }
    }

    // Debugging function to print the Fenwick Tree array
    void printTree() {
        for (int i = 1; i <= n; ++i) {
            cout << "c[" << i << "] = " << c[i] << endl;
        }
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, m;
    cin >> n >> m;
    vector<int> a(n);
    for (auto &it : a)
        cin >> it;
    FenwickTree<int> ft(n);
    ft.build(a);
    while (m--) {
        int operation;
        cin >> operation;
        if (operation == 1) { // point update
            int x, k;
            cin >> x >> k;
            ft.update(x, k);
        } else { // range sum
            int l, r;
            cin >> l >> r;
            cout << ft.rangeQuery(l, r) << endl;
        }
    }
    return 0;
}