树状数组(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");
}