携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第20天,点击查看活动详情
树状数组是一个能够快速解决单点修改、区间查询的数据结构,具有代码短、不容易出bug的优点。树状数组能解决的问题线段树都能解决,但线段树能解决的问题树状数组大部分都解决不了,例如更新并维护区间最值问题。
lowbit(i)为i这颗子树节点个数,i+lowbit(i)为其父节点编号,i的二进制中第一个非零数的位置为其高度。
以acwing1264为例,介绍常用模板:
#include <iostream>
#include <cstdio>
#include <algorithm>
#define lowbit(x) (x&-x)//这里一定要加括号,防止运算优先级不同带来的问题
using namespace std;
//以acwing1264为例
//树状数组可以快速处理单点修改与区间查询操作
//如果涉及到区间修改则维护差分上的树状数组,若同时涉及区间查询则需维护两个树状数组
int c[100005], n, m, a[100005], s[100005];
void add(int x, int v)//单点修改
{
for(int i = x; i <= n; i+=lowbit(i))//i+lowbit(i)是i父节点编号
c[i] += v;
}
int getsum(int x)//求前缀和
{
int ans = 0;
for(int i = x; i >= 1; i-=lowbit(i))
ans += c[i];
return ans;
}
signed main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
s[i] = s[i-1] + a[i];
}
//建立树状数组
for(int i = 1; i <= n; i++)
c[i] = s[i]-s[i-lowbit(i)];
//也可以n次add(i, t)来建立,只是有些慢,如果空间充足的话还是开s[]数组吧
int op, x, y;
for(int i = 1; i <= m; i++)
{
scanf("%d%d%d", &op, &x, &y);
if(op == 1)
add(x, y);
else
printf("%d\n", getsum(y)-getsum(x-1));
}
return 0;
}
常见的容易出bug的地方:
-
n要定义为全局变量,如果同时定义n为局部变量和全局变量,读入n时优先给局部变量赋值,导致全局变量n没有被赋值。
-
维护的树状数组开小了。树状数组大小应该是操作数的最大值。
-
混淆各变量含义。有时n并不是更新上限,要根据题目具体分析。
-
树状数组中不可以涉及到0,因为lowbit(0)还是0,会导致死循环,如果有0的话就所有数都加1。