树状数组

树状数组 ( \(\text{fenwick tree}\) ) 是主要用于前缀信息维护的一维数组 ——《信息学奥林匹克辞典》

基础树状数组

维护信息

维护一个数列的元素的操作

可进行的操作

  1. 单点修改,即修改数列中其中一个元素的值

  2. 区间查询,即查询数列中连续一段区间的值进行某种运算

存储方法

树状数组的本体是一个数组

它巧妙地运用了二进制的思想,将树状数组本体中的每一个元素所管辖的范围由其下标决定。

具体地,若设其本体为 \(\text{C++}\) 中的数组 ft,则 ft[i] 所管辖的范围为 \([\) i - lowbit(i) + 1 , i \(]\)

若还不理解的话,可看下面的图:

树状数组

操作方法

单点修改

易发现,修改一个点,只需修改它本身与其祖先即可。

那我们怎么快速得到一个数的父亲节点呢?

既然自己管辖了 lowbit(i) 个,那它的兄弟肯定也管辖了 lowbit(i) 个节点,而我们发现,因为此时我们已经是这一列的最上层了,所以,它的兄弟的下标实际上就是它们父亲的下标!

所以:fa = i + lowbit(i)

又因为它最多有 \(\log n\) 个祖先,所以它时间复杂度为 \(\Theta(\log n)\)

区间查询

我们先思考一个更简单的问题:怎么查询 \(1\)\(x\) 的运算起来的值呢?

由于我们要尽可能地应用我们已知的值,则试图找到一个满足 \(\log_2k\in\mathbb Z 且 k \le n\),此时,我们可以把 ft[k] 的贡献算上,然后问题就转化成了查询 \(k+1\)\(x\) 的运算起来的值,注意到因为 \(\log_2k \in \mathbb Z\),所以可以用 \(1\)\(x\) 的方法来算。

回到原问题,我们就可以用前缀和的思想来解决它了。

容易发现,每计算一次,区间大小都至少会减半,故其时间复杂度为 \(\Theta(\log n)\)

代码

以加法为例。

int n;
int ft[MAXN];
int lowbit(int x) {
    return x & (-x);
}
int sum(int x, int y) {
    int s = 0;
    for (int i = y; i >= 1; i -= lowbit(i)) {
        s += ft[i];
    }
    for (int i = x - 1; i >= 1; i -= lowbit(i)) {
        s -= ft[i];
    }
    return s;
}
void add(int x, int v) {
    for (int i = x; i <= n; i += lowbit(i)) {
        ft[i] += v;
    }
}

差分+树状数组

可进行的操作

  1. 区间修改

  2. 单点查询

操作方法

容易想到使用差分来把它转化成基础树状数组,即用它维护差分数组,然后单点查询变成了区间查询,区间修改变成了单点修改。

代码

int n;
int ft[MAXN];
int lowbit(int x) {
    return x & (-x);
}
int sum(int x) {
    int s = 0;
    for (int i = x; i >= 1; i -= lowbit(i)) {
        s += ft[i];
    }
    return s;
}
void add(int x, int y, int v) {
    for (int i = x; i <= n + 1; i += lowbit(i)) {
        ft[i] += k;
    }
    for (int i = y+1; i <= n + 1; i += lowbit(i)) {
        ft[i] -= k;
    }
}

二维树状数组

维护信息

它所维护的是矩阵内的元素的操作。

可进行的操作

  1. 单点修改

  2. 子矩阵查询

存储方法

一个二维数组,所维护的东西简单粗暴地把树状数组里套了个树状数组。

操作方法

实际上也很简单粗暴,就是把每个元素当作一个树状数组再进去操作。

注意,它的时间复杂度为 \(\Theta(\log^2 n)\)

代码

int n, ft[MAXN][MAXN];
int lowbit(int x) {
    return x & (-x);
}
void add(int x, int y, int z) {
    for (int i = x; i <= n; i += lowbit(i))
        for (int j = y; j <= n; j += lowbit(j))
            ft[i][j] += z;
}
int query(int x, int y) {
    int res = 0;
    for (int i = x; i >= 1; i -= lowbit(i))
        for (int j = y; j >= 1; j -= lowbit(j))
            res += ft[i][j];
    return res;
}

  1. 文中出现的 lowbit 指的是该数在二进制状态下的最后一个 \(1\) 所表示的数,在补码下可用 x & (-x) 来计算
posted @ 2023-09-20 21:20  LightningCreeper  阅读(20)  评论(0编辑  收藏  举报