树状数组(Binary Indexed Tree(BIT), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构。主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值;经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值(如果加入多个辅助数组则可以实现区间修改与区间查询)。——百度百科
树状数组作用就是给定一个数组,在log(n)时间内求数组内某个区间的和,并且可以在log(n)时间内修改其中的某个元素。本文只研究修改单个值,区间求和的情况。
需要维护一个C数组,如下图所示:
令这棵树的结点编号为C1,C2...Cn。令每个结点的值为这棵树的值的总和,那么容易发现:
C1 = A1
C2 = A1 + A2
C3 = A3
C4 = A1 + A2 + A3 + A4
C5 = A5
C6 = A5 + A6
C7 = A7
C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8
...
C16 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8 + A9 + A10 + A11 + A12 + A13 + A14 + A15 + A16
这里有一个有趣的性质:
设节点编号为x,那么这个节点管辖的区间为2^k(其中k为x二进制末尾0的个数)个元素。因为这个区间最后一个元素必然为Ax,
所以很明显:Cn = A(n – 2^k + 1) + ... + An
例如:C[6]这个结点,6的二进制为110,末尾有1个0,所以它的管辖区间是2^1 = 2个元素,即5和6。
求2^k的简便方法是使用位运算:
int lowerbit(int x)
{
return x&(x^(x–1));
}
利用机器补码特性,也可以写成:
int lowerbit(int x)
{
return x&-x;
}
解释:lowbit(k)就是把k的二进制的高位1全部清空,只留下最低位的1,比如10的二进制是1010,则lowbit(k)=lowbit(1010)=0010(2进制),介于这个lowbit在下面会经常用到,这里给一个非常方便的实现方式,比较普遍的方法lowbit(k)=k&-k,这是位运算,我们知道一个数加一个负号是把这个数的二进制取反+1,如-10的二进制就是-1010=0101+1=0110,然后用1010&0110,答案就是0010了,也就是2^k的结果。
修改某个值时:
如果要修改一个值,那么需要修改所有管辖这个数的C[]数组的值。
比如要修改a[3],由于a[3]同时被c[3]、c[4]、c[8](假设只有8个数字)管辖,那么更新a[3]的时候,就需要同时修改c[3]、c[4]、c[8]。
修改代码:
void add(int k,int num) //将第k个元素加上num
{
while(k<=n)
{
tree[k]+=num;
k+=k&-k;
}
}
求和:
如果求1~n这个区间和的话,只需要将log(n)个C[]数组中的值想加即可。比如要求1~6这个区间的和,那就只需将C[6]和cC[4]相加即可。
求和代码:
int read(int k)//1~k的区间和
{
int sum=0;
while(k)
{
sum+=tree[k];
k-=k&-k;
}
return sum;
}
参考:http://www.cnblogs.com/ECJTUACM-873284962/p/6380245.html
整体代码:
#include <stdio.h>
#include <string.h>
#define N 1005
int c[N], n;
int lowbit(int x){
return x&(-x);
}
void add(int k, int x){
while(k <= n){
c[k] += x;
k += lowbit(k);
}
}
int sum(int k)//求前k项和
{
int s=0;
while(k>0)
{
s+=c[k];
k-=lowbit(k);
}
return s;
}
int main(){
int q, x, a, b;
while(~scanf("%d%d", &n, &q)){
memset(c, 0, sizeof(c));
for(int i = 1; i <= n; i ++){
scanf("%d", &x);
add(i,x);
}
while(q--){
scanf("%d%d", &a, &b);
int ans = sum(b) - sum(a-1);
printf("%d\n", ans);
}
}
return 0;
}
上面讲的都是一维树状数组,树状数组也可以扩展到二维,即求和时改为求某个子矩阵的和。
举个例子来看看C[][]的组成。
设原始二维数组为:
A[][]={{a11,a12,a13,a14,a15,a16,a17,a18,a19},
{a21,a22,a23,a24,a25,a26,a27,a28,a29},
{a31,a32,a33,a34,a35,a36,a37,a38,a39},
{a41,a42,a43,a44,a45,a46,a47,a48,a49}};
那么它对应的二维树状数组C[][]呢?
记:
B[1]={a11,a11+a12,a13,a11+a12+a13+a14,a15,a15+a16,...} 这是第一行的一维树状数组
B[2]={a21,a21+a22,a23,a21+a22+a23+a24,a25,a25+a26,...} 这是第二行的一维树状数组
B[3]={a31,a31+a32,a33,a31+a32+a33+a34,a35,a35+a36,...} 这是第三行的一维树状数组
B[4]={a41,a41+a42,a43,a41+a42+a43+a44,a45,a45+a46,...} 这是第四行的一维树状数组
那么:
C[1][1]=a11,C[1][2]=a11+a12,C[1][3]=a13,C[1][4]=a11+a12+a13+a14,c[1][5]=a15,C[1][6]=a15+a16,...
这是A[][]第一行的一维树状数组
C[2][1]=a11+a21,C[2][2]=a11+a12+a21+a22,C[2][3]=a13+a23,C[2][4]=a11+a12+a13+a14+a21+a22+a23+a24,
C[2][5]=a15+a25,C[2][6]=a15+a16+a25+a26,...
这是A[][]数组第一行与第二行相加后的树状数组
C[3][1]=a31,C[3][2]=a31+a32,C[3][3]=a33,C[3][4]=a31+a32+a33+a34,C[3][5]=a35,C[3][6]=a35+a36,...
这是A[][]第三行的一维树状数组
C[4][1]=a11+a21+a31+a41,C[4][2]=a11+a12+a21+a22+a31+a32+a41+a42,C[4][3]=a13+a23+a33+a43,...
这是A[][]数组第一行+第二行+第三行+第四行后的树状数组
C[]数组在二维树状数组中需要同时管辖行和列,管辖的范围与一维相似,不论是行还是列都是管辖lowbit(i)的范围。
修改代码:
void modify(int x,int y,int data)
{
for(int i=x;i<MAXN;i+=lowbit(i))
for(int j=y;j<MAXN;j+=lowbit(j))
c[i][j]+=data;
}
求和代码:
int get_sum(int x,int y)
{
int res=0;
for(int i=x;i>0;i-=lowbit(i))
for(int j=y;j>0;j-=lowbit(j))
res+=c[i][j];
return res;
}
整体代码:
#include<iostream>
#include <stdio.h>
#include <string.h>
#include<algorithm>
using namespace std;
const int MAXN = 1010;
int c[MAXN][MAXN];
int lowbit(int x)
{
return x & (-x);
}
void modify(int x,int y,int data)
{
for(int i=x;i<MAXN;i+=lowbit(i))
for(int j=y;j<MAXN;j+=lowbit(j))
c[i][j]+=data;
}
int get_sum(int x,int y)
{
int res=0;
for(int i=x;i>0;i-=lowbit(i))
for(int j=y;j>0;j-=lowbit(j))
res+=c[i][j];
return res;
}
int main()
{
int n,x,y,x1,y1, date;
char str[5];
scanf("%d",&n);
memset(c,0,sizeof(c));
while(n--)
{
scanf("%s",str);
if(strcmp(str,"add")==0)
{
scanf("%d%d%d",&x,&y, &date);
modify(x,y,date);
}
else {
scanf("%d %d %d %d",&x,&x1,&y,&y1);
int ans=get_sum(x1,y1)-get_sum(x-1,y1)-get_sum(x1,y-1)+get_sum(x-1,y-1);
printf("%d\n",ans);
}
}
return 0;
}