对于一个n元素的数组A[n],可执行如下操作(单点修改,区间查询):
Add(I, d):让A[i]变成A[i]+d。
Query(L, R):返回A[L]+A[L+1]+…+A[R]。
注意:树状数组只能计算A[1]开始的和,A[0]这个元素是不能用的。上面单点修改和区间查询操作复杂度都是O(logn)。
其实树状数组还可以处理区间更新,单点查询的问题。如HDU 1556 Color the ball,但是此类问题还是用线段树做比较直观。
代码:
const int maxn=10000+5;//最大元素个数 int n;//元素个数 int c[maxn];//c[i]==A[i]+A[i-1]+...+A[i-lowbit(i)+1] //返回i的二进制最右边1的值 int lowbit(int i) { return i&(-i); } //返回A[1]+...A[i]的和 int sum(int i) { int res=0; while(i>0) { res += c[i]; i -= lowbit(i); } return res; } //令A[i] += val void add(int i,int val) { while(i<=n) { c[i] += val; i += lowbit(i); } }
注意,刚开始c数组要初始全0,然后每读入一个数A[i]就执行一步add(i, A[i])来进行真正的初始化。
且树状数组处理的数组A[n]是从小标1开始的,也即A[0]是一个没有用的元素。
例题: HDU 1166 敌兵布阵(简单树状数组):基本应用
http://acm.hdu.edu.cn/showproblem.php?pid=1166
题意:
Input
第一行一个整数T,表示有T组数据。
每组数据第一行一个正整数N(N<=50000),表示敌人有N个工兵营地,接下来有N个正整数,第i个正整数ai代表第i个工兵营地里开始时有ai个人(1<=ai<=50)。
接下来每行有一条命令,命令有4种形式:
(1) Add i j,i和j为正整数,表示第i个营地增加j个人(j不超过30)
(2)Sub i j ,i和j为正整数,表示第i个营地减少j个人(j不超过30);
(3)Query i j ,i和j为正整数,i<=j,表示询问第i到第j个营地的总人数;
(4)End 表示结束,这条命令在每组数据最后出现;
每组数据最多有40000条命令Output
对第i组数据,首先输出“Case i:”和回车,
对于每个Query询问,输出一个整数并回车,表示询问的段中的总人数,这个数保持在int以内。代码:
#include<cstdio> #include<cstring> using namespace std; const int MAXN=50000; int c[MAXN+1]; int lowbit(int x) { return x&(-x); } int sum(int x)//此处x可以为0 { int res=0; while(x>0) { res+=c[x]; x-=lowbit(x); } return res; } void add(int x,int v)//x必须大于0 { while(x<=MAXN) { c[x] +=v; x+=lowbit(x); } } int main() { int T,kase=1; scanf("%d",&T); while(T--) { printf("Case %d:\n",kase++); int n; scanf("%d",&n); memset(c,0,sizeof(c)); for(int i=1;i<=n;i++) { int v; scanf("%d",&v); add(i,v); } char str[10]; while(scanf("%s",str)==1) { if(str[0]=='E') break; else if(str[0]=='Q') { int i,j; scanf("%d%d",&i,&j); printf("%d\n",sum(j)-sum(i-1)); } else if(str[0]=='A') { int x,v; scanf("%d%d",&x,&v); add(x,v); } else if(str[0]=='S') { int x,v; scanf("%d%d",&x,&v); add(x,-v); } } } return 0; }
注:树状数组一般只能开1e5以下的数据