树状数组

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

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值