树状数组

题目 树状数组模板1 题目大意:给定一个序列,要求支持两种操作:1.将某个数加上 \(x\),2.查询区间某部分的和。

题目 树状数组模板2 题目大意:给定一个序列,要求支持两种操作:1.将区间内的每个数加上 \(x\),2.查询某个数。

分析 由于第一题与第二题实际上是等价的(稍后会说明),我们以下仅讨论第一题。我们先以最简单的数组来考虑,显然操作1是 \(O(1)\),操作2是 \(O(n)\) 的,总体复杂度 \(O(nm)\),很明显会超时。这时我们就会想到用前缀和来优化操作2,这样的话操作2是 \(O(1)\) 的,但是操作1是 \(O(n)\) 的,总体复杂度仍为 \(O(nm)\),会超时。有没有一种数据结构,可以使操作1和操作2都是 \(O(1)\) 的呢?仅目前来看是不存在的。于是我们退而求其次寻找一种数据结构,使操作1和操作2都是 \(O(\log n)\) 的。

树状数组(又称二叉索引树,Binary Indexed Tree,BIT)是一类最基础的树形数据结构。它支持 \(O(\log n)\) 进行区间修改单点查询或区间查询单点修改,亦即上述两道题的操作。具体来说,树状数组自身本质上仍是一个数组。我们令 \(a\) 为原数组,\(c\) 为树状数组,假定 \(i\) 二进制最后有 \(j\) 个0,则有 \(c_i=\sum_{k=0}^{2^j}a_{i-k}\)

树状数组
(图片来自网络,侵删)

不难发现,现在我们更改与查询的时候只需更改包含着对象的位置就可以了,这样的位置不会超过 \(\log n\) 个(为什么),所以我们只需 \(O(1)\) 从一个位置找到包含原对象的另一个位置就行了。

如果当前要修改 \(1(0001)\),则要修改 \(1(0001),2(0010),4(0100),8(1000)\)

如果当前要修改 \(5(0101)\),则要修改 \(5(0101),6(0110),8(1000)\)

如果当前要查询 \(7(0111)\) 的前缀和,则要查询 \(7(0111),6(0110),4(0100)\)

如果当前要查询 \(5(0101)\) 的前缀和,则要查询 \(5(0101),4(0100)\)

不难发现,修改一个数时,下一个要修改的目标位置就是当前位置加上当前位置的最后一位,如 \(6\rightarrow 8(0110+0010=1000)\);查询一个数时,下一个要修改的目标位置就是当前位置减去当前位置的最后一位,如 \(5\rightarrow 4(0101-0001=0100)\)。那么现在问题转化为了如何 \(O(1)\) 求出某个数二进制最后一位,那便是lowbit函数:

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

这个函数可以以 \(O(1)\) 的优秀复杂度求出某个数二进制的最后一位(比如 \(3(0011)\rightarrow 1(0001),6(0110)\rightarrow 2(0010)\)),证明只需一点点基础的二进制编码,留给读者自证。

那么现在我们就可以愉快地写出第一道题的代码。而对于第二题,不难发现,只要对原数列进行差分,就可以将区间修改转变为单点修改,单点查询转变为区间查询。

代码
第一题:

#include<bits/stdc++.h>
using namespace std;

const int maxn = 5E+5 + 5;

int n, m;
int c[maxn];

int Read()
{
    int x = 0, op = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9') {
        if(ch == '-') op = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9') {
        x = (x << 3) + (x << 1) + (ch - '0');
        ch = getchar();
    }
    return x * op;
}

inline int lowbit(int x) { return x & (-x); }
inline void Add(int pos, int x) { while(pos <= n) c[pos] += x, pos += lowbit(pos); }

int Query(int pos)
{
    int res = 0;
    while(pos) {
        res += c[pos];
        pos -= lowbit(pos);
    }
    return res;
}

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++i) Add(i, Read());

    while(m--) {
        int op = Read(), x = Read(), y = Read();

        if(op == 1) Add(x,y);
        else printf("%d\n", Query(y) - Query(x - 1));
    }
}

第二题:

#include<bits/stdc++.h>
using namespace std;

const int maxn = 5E+5 + 5;

int n, m;
int a[maxn], c[maxn];

int Read()
{
    int x = 0, op = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9') {
        if(ch == '-') op = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9') {
        x = (x << 3) + (x << 1) + (ch - '0');
        ch = getchar();
    }
    return x * op;
}

inline int lowbit(int x) { return x & (-x); }
inline void Add(int pos, int x) { while(pos <= n) c[pos] += x, pos += lowbit(pos); }

int Query(int pos)
{
    int res = 0;
    while(pos) {
        res += c[pos];
        pos -= lowbit(pos);
    }
    return res;
}

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++i) a[i] = Read();

    while(m--) {
        int op = Read(), x = Read(), y, z;

        if(op == 1) y = Read(), z = Read(), Add(x, z), Add(y + 1, -z);
        else printf("%d\n", a[x] + Query(x));
    }
}
posted @ 2019-11-10 00:35  whx1003  阅读(96)  评论(0编辑  收藏  举报