树状数组 - 高级数据结构

引入

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

col1 修改(add) 求和查询(query) 构造(init)
树状数组 O(logn) O(logn) O(n)// 
数组 O(1) O(n) O(nlogn)// n

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

实现

lowbit()

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

举个例子:

(1)lowbit(22)10=lowbit(10110)2(2)=(10)2(3)=2

如何实现lowbit()呢?

 10110=0100101001+1=01010&  01010& 10110000102

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

于是我们可以简化代码:

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

树状数组

结构:树

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

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

C[i]=lowbit(i)

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

fatherC[i]=i+lowbit(i)

举个栗子:

3=(011)2{father3=lowbit(011)+(011)2=(1+011)2=(100)2=4father3=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 @   MoyouSayuki  阅读(59)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
:name :name
点击右上角即可分享
微信分享提示