【算法笔记】树状数组

树状数组是一种数据结构,兼顾单点修改和区间查询的效率,时间复杂度均为O(logn)。它通过低bit操作优化更新和查询过程,解决了普通数组和前缀和在特定操作比例下的性能问题。博客内容包括树状数组的原理、实现、应用示例以及动态求连续区间和的代码展示。
摘要由CSDN通过智能技术生成

树状数组(Binary Index Tree, BIT)支持两种操作,时间复杂度均为O(logn):
单点修改:更改数组中一个元素的值
区间查询:查询一个区间内所有元素的和


树状数组的引入

对于普通数组而言,单点修改的时间复杂度是O(1),但区间求和的复杂度是O(n)
对于前缀和而言,区间求和的复杂度是O(1),但单点修改的时间复杂度是O(n)

在单点修改与区间查询比例为1:K的要求下
普通数组的时间复杂度为 (n+K*O(n)) / (K+1) 综合复杂度为O(n)
前缀和的时间复杂度为 (O(n)+K*n) / (K+1) 综合复杂度为O(n)

因此我们希望找到一种折中的方法:无论单点修改还是区间查询,他都能不那么慢的完成。

注意到对 [a,b] 进行区间查询只需查询 [1,b] 和 [1,a)然后相减即可(前缀和就是这样进行区间查询的),所以我们可以把区间查询问题转化为求前n项和的问题。
————————————————————
关于数组的维护,有个很自然的想法:可以用一个数组 C 维护若干个小区间,单点修改时,只更新包含这一元素的区间;求前n项和时,通过将区间进行组合,得到从1到n的区间,然后对所有用到的区间求和。 实际上,设原数组是 A ,如果Ci 维护的区间是 [Ai,Ai] ,此结构就相当于普通数组(还浪费了一倍内存);如果 Ci 维护的区间就是 [1,Ai] ,此结构就相当于前缀和。
————————————————————
现在我们试图寻找一种结构,一方面,单点修改时需要更新的区间不会太多;另一方面,区间查询时需要用来组合的区间也不会太多。
————————————————————

在这里插入图片描述

树状数组的结构:
在这里插入图片描述

树状数组的实现

lowbit(x)

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

单点修改

inline void update(int x, int v)
{
    for (int i = x; i < MAXN; i += lowbit(pos))
        tr[i] += v;
}

前缀和

inline int query(int x)
{
    int res = 0;
    for (int i = x; i; i -= lowbit(i))
        res += tr[i];
    return res;
}

区间查询

inline int query(int l, int r)
{
    return query(r)-query(l);
}

树状数组的应用

动态求连续区间和

给定 n 个数组成的一个数列,规定有两种操作,一是修改某个元素
,二是求子数列 [a,b] 的连续和。

输入格式
第一行包含两个整数 n 和 m,分别表示数的个数和操作次数。

第二行包含 n 个整数,表示完整数列。

接下来 m 行,每行包含三个整数 k,a,b (k=0,表示求子数列[a,b]的和;
k=1,表示第 a 个数加 b)。

数列从 1 开始计数。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 10;

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

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

void add(int x, int v)
{
    for (int i = x; i <= n; i += lowbit(i))
        tr[i] += v;
}

int query(int x)
{
    int res = 0;
    for (int i = x; i; i -= lowbit(i))
        res += tr[i];
    return res;
}

int query(int l, int r)
{
    return query(r) - query(l);
}

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

    while (m--)
    {
        int k, x, y;
        scanf("%d%d%d", &k, &x, &y);
        if (k == 0)
            cout <<query(x-1,y)<< endl;
        else
            add(x, y);
    }
    //system("pause");
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值