树状数组笔记

68 阅读2分钟

时隔三年,再次重拾树状数组,不禁想起了那段上课时天天摸鱼的日子,唉唉,虽然后悔自己当时没有好好认真学,但似乎也不可苛责过去的自己。

言归正传,刚刚打了一个最基本的树状数组模板,回顾一下树状数组的原理:

对于一个树状数组,我们初始化其大小为序列长度 nn ,对于每个位置 ii ,它维护的是区间 [i(i&i)+1,i][i-(i\&-i)+1,i] 的和,即我们令每个节点负责长度为 2k2^k 的区间和,其中的 kk 怎么取?就使用 ii 的最低位的 11 的位置,这样的话我们就可以通过 lognlogn 次跳跃来求出某个区间的前缀和或者更新某个位置的值。

模板代码如下:

class fenwick_tree
{
private:
    int n;
    vector<int> bit;

public:
    fenwick_tree(const int n)
    {
        this->n = n;
        bit.resize(n + 1, 0);
    }

    int lowbit(int x)
    {
        return x & (-x);
    }

    void update(int idx, int delta)
    {
        while (idx <= n)
        {
            bit[idx] += delta;
            idx += lowbit(idx);
        }
    }

    int query(int idx)
    {
        int sum = 0;
        while (idx)
        {
            sum += bit[idx];
            idx -= lowbit(idx);
        }

        return sum;
    }

    int query_range(int left, int right)
    {
        return query(right) - query(left - 1);
    }

    void initialize(vector<int> &arr)
    {
        for (int i = 1; i <= n; i++)
            update(i, arr[i]);
    }
};

值得一提的是,线段树和树状数组在这种特定问题上的效率差距还是比较明显的,尤其是单点更新和求区间前缀和?

image.png

可以看到还是有差距的,这是线段树的代码:

class segment_tree
{
private:
    int n;
    vector<int> tree;
    vector<int> vals;

    void push_up(int node)
    {
        int left_node = (node << 1);
        int right_node = (node << 1 | 1);

        tree[node] = tree[left_node] + tree[right_node];
    }

    void build(int node, int start, int end)
    {
        if (start == end)
        {
            tree[node] = vals[start];
            return;
        }

        int mid = ((start + end) >> 1);
        int left_node = (node << 1);
        int right_node = (node << 1 | 1);

        build(left_node, start, mid);
        build(right_node, mid + 1, end);

        push_up(node);
    }

    void update_point(int node, int start, int end, int idx, int val)
    {
        if (start == end)
        {
            tree[node] += val;
            return;
        }

        int mid = ((start + end) >> 1);
        int left_node = (node << 1);
        int right_node = (node << 1 | 1);

        if (idx <= mid)
            update_point(left_node, start, mid, idx, val);
        else
            update_point(right_node, mid + 1, end, idx, val);

        push_up(node);
    }

    int query_range(int node, int start, int end, int l, int r)
    {
        if (start > r || end < l)
            return 0;
        if (start >= l && end <= r)
            return tree[node];

        int mid = ((start + end) >> 1);
        int left_node = (node << 1);
        int right_node = (node << 1 | 1);

        return query_range(left_node, start, mid, l, r) + query_range(right_node, mid + 1, end, l, r);
    }

public:
    segment_tree(const int n, const vector<int> &arr)
    {
        this->n = n;
        vals = arr;
        tree.resize((n << 2) + 1);
        build(1, 1, n);
    }

    void update(int idx, int val)
    {
        update_point(1, 1, n, idx, val);
    }

    int query(int l, int r)
    {
        return query_range(1, 1, n, l, r);
    }
};

而且树状数组实现也比较简单(