树状数组模板总结

342 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 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的地方:

  1. n要定义为全局变量,如果同时定义n为局部变量和全局变量,读入n时优先给局部变量赋值,导致全局变量n没有被赋值。

  2. 维护的树状数组开小了。树状数组大小应该是操作数的最大值。

  3. 混淆各变量含义。有时n并不是更新上限,要根据题目具体分析。

  4. 树状数组中不可以涉及到0,因为lowbit(0)还是0,会导致死循环,如果有0的话就所有数都加1。