树状数组 - 高级数据结构

引入

数列操作
本题是 树状数组模板题,这个算法最大的优点是查询速度很快

col1 修改(add) 求和查询(query) 构造(init)
树状数组 \(O(\log n)\) \(O(\log n)\) \(O(n)\small\color{ForestGreen}{//\ 读入数据}\)
数组 \(O(1)\) \(O(n)\) \(O(n\log n)\small\color{ForestGreen}{//\ n次修改}\)

所以树状数组适合数据范围大,查询次数多的时候使用

实现

lowbit()

见多识广的同学都知道这个函数,它的目的是求出一个数二进制中最低位的1的位置,并将位置转化成十进制

举个例子:

\[\begin{align} lowbit(22)_{10} &= lowbit(10110)_2\\ &= (10)_2\\ &= 2 \end{align} \]

如何实现lowbit()呢?

\[首先,将原数取反:\\ ~10110 = 01001\\ 再加一:\\ 01001+1 = 01010\\ 将新数与原数进行与运算(\&)\\ \begin{align} \ \ 01010\nonumber\\ \&\ 10110\nonumber\\ ————\nonumber\\ 00010\nonumber\\ 也就是2\nonumber \end{align} \]

众所周知,补码本质就是原码的反码加一

于是我们可以简化代码:

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

树状数组

结构:树

树状数组其实是前缀和的变形,或者说,前缀和本身就是没有修改操作的树状数组。

聪明绝顶(物理)的科学家发现C数组(存树)的二进制下标对应了它所涵盖的数的数量,得出公式:

\[C[i]涵盖数的数量 = lowbit(i) \]

根据一根头发的观察发现,要找到一个点的父亲,我们需要将此点的二进制下标加上\(lowbit(下标)\)

\[father_{C[i]} = i + lowbit(i) \]

举个栗子:

\[∵3 = (011)_2\\ ∴\left\{ \begin{align} father_3 &= lowbit(011) + (011)_2\nonumber \\ &= (1 + 011)_2\nonumber\\ &= (100)_2 \nonumber\\ &= 4\nonumber \end{align}\right.\\ ∴father_3 = 4\\ \]

我们前面说过,树状数组支持两种操作(修改和求和),下方展开说明

修改

结合以上知识,我们直接从\(i\)开始循环找到它的每个父辈,加上要修改的值即可

void add(int a, int b)
{
    for(; a <= n; a += lowbit(a))
        tree[a] += b;
}

查询求和

大概意思是将\(C[i]\)分割成小的儿子节点

我也不知道怎么解释,看动图吧

LL query(int a)
{
    LL sum = 0;
    while(a >= 1)
    {
        sum += tree[a]; // 求和
        a -= lowbit(a); // 分割
    }
    return sum;
}

构造

遍历整个待构造的数组,把每个点都加入树状数组即可

void init()
{
    for(int i = 1; i <= n; i++) // 遍历
    {
        LL a;
        cin >> a;
        add(i, a); // 加入树状数组
    }
}

对了,记得开long long

Talk is cheap, show me the code!
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;

int n, m;
LL tree[1000010];

inline LL read() // 快读,不然不保证能稳定过
{
    LL s = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar();   
    }
    while (ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
    return f * s;
}

int lowbit(int x)
{
    return x & -x;
}
LL query(int a) // 查询求和
{
    LL sum = 0;
    while(a >= 1)
    {
        sum += tree[a];
        a -= lowbit(a);
    }
    return sum;
}

void add(int a, int b) // 加入树状数组
{
    for(; a <= n; a += lowbit(a))
        tree[a] += b;
}

void init()
{
    for(int i = 1; i <= n; i++)
    {
        LL a;
        cin >> a;
        add(i, a);
    }
}

int main()
{
    n = read();
    m = read();
    init();
  
    for(int i = 1; i <= m; i ++)
    {
        int op;
        op = read();
        LL a, b;
        a = read(), b = read(); // 读入
        if(op == 1) // 哪个操作呢
            add(a, b); // 加边加边加边
        else
            printf("%lld\n", query(b) - query(a - 1));  // 1~b的区间和 - 1~a-1的区间和即为 a~b的区间和
// 然后并查集查询(大雾)
    }
    return 0;
}

蒟蒻的题解难免有误,恳请dalao指出!

posted @ 2022-07-28 20:59  MoyouSayuki  阅读(54)  评论(2编辑  收藏  举报
:name :name