树状数组

来源:互联网 发布:自行车 知乎 编辑:程序博客网 时间:2024/05/16 23:41

题目链接


树状数组推荐看训练指南的说明,90%能看懂。不能看懂再往下看。


求a1,a2...an的和,下面看 CS 97SI: Introduction to Competitive Programming Contests  的PPT里的图片。

思想直接看原文:


Each internal node stores the sum of values of its children
– e.g., Red node stores item[5] + item[6]


Main idea: choose the minimal set of nodes whose sum gives the desired value

I We will see that
– at most 1 node is chosen at each level so that the total number of nodes we look at is log 2 n
– and this can be done in O(logn) time



计算方法


//版本2

树状数组是对数组元素修改和查询都是O(logn)的结构,主要用于查询任意两位之间的所有元素之和,可参考百度


最基本的是在有修改元素的情况下求区间内元素和

int lowbit(int t){    return t&(-t);}

lowbit(i):将i转化成二进制数之后,只保留最低位的1及其后面的0,截断前面的内容,然后再转成10进制数

X^:X取反(符号我这里定的,不是什么官方符号)

lowbit(x)实际上是提取x从左往右数的最后一个1。
设x为a1b,a1b中的1位最后一个1,a和b都表示一串01串(当然b全为0或者不存在),如13=1101则a=110,b不存在。
对一个数x取负相当于该数的二进制取反再加1,所以-x = (a^- 1^ b^) = (a^ 0 1...1) +1 = (a^ 1 0...0)。
则 x & (-x) = (a 1 00...) & (a^ 1 00...) = (0...0  1 0...0)。结果就是保留最后个1和后面的0的十进制答案。


10进制值二进制值lowbit()值210231114100451011
那么 i + lowbit( i ) 可以求出父节点的下标

把子树向右对称翻得出一些空白结点,树变成完全二叉树,看图可知父节点下标和空白结点下标相同



i            lowbit(i)    i+lowbit(i) 224314448516

像 i == 5时,父节点下标是6,表示c[5]在c[6]管辖下,c[6]还管了个A[6],c[6] = c[5] + a[6]

void add(int i,int num){    while(i <= n)    {        c[i] += num;        i += lowbit(i);    }}
给a[ i ]加值,则要把c[ i ] 和 管理c[ i ]的那些 c[ x ] 也加上相同的值,如给1 号元素加1,则要把c[1],c[2],c[4],c[8]都加1。add()代码中 i += lowbit(I)就是父节点下标

那么减值就是参数为负数就行

int getSum(int i){    int sum = 0;    while(i > 0)    {        sum += c[i];        i -= lowbit(i);    }    return sum;}
 i - lowbit( i ) 求出跟 另一个c[ x ] ,c[ x ] 管理了 小于 i 的 a [ i ] 中,c[ i ] 所没有管理到的 a[ i ],那么把c [ i ] 和 c [ x ]加起来就是A[1]...A[ i ]的和

i12345678i - lowbit(i)00204460如求a[1] ... a[6],那么求c[6] + c[ 6-lowbit(6) ]即 c[6] + c[4]即可


还不过瘾就看看http://www.hawstein.com/posts/binary-indexed-trees.html吧,是Topcoder文章的翻译


这种直接插入求和,修改值的函数( 这里的add() )的循环是从下往上,而求和函数是从上往下,所以也被归类为 向上查询,向下求和的类型


下面来道向下查询,向上求和的题目

HDU 1556 Color the ball

题目很好理解,代码实现不需要a数组。但add()一改就是改一串相关联的,那么要改区间[a,b]元素的值,可以这样:把[1,b]元素值+1,再把[1,a-1]值-1,抵消后就是把[a,b]的元素值+1,因为改的是小于等于 i 的部分值,所以是向下查询

#include <iostream>#include <cstdio>#include <cmath>#include <cstring>#include <cstring>#include <algorithm>#include <cstdlib>#define FOR(i,n) for(int i=0;i<(n);i++)#define ll __int64#define NMAX 100001using namespace std;int c[NMAX],n;int lowbit(int t){    return t&(-t);}void add(int i,int num){    while(i > 0)    {        c[i] += num;        i -= lowbit(i);    }}int getSum(int i){    int sum = 0;    while(i <= n)    {        sum += c[i];        i += lowbit(i);    }    return sum;}int main(){  //  freopen("in.txt","r",stdin);    int a,b;    while(scanf("%d",&n)!=EOF,n)    {        memset(c,0,sizeof(c));        for(int i=0;i<n;i++)        {            scanf("%d%d",&a,&b);            add(b,1);            add(a-1,-1);        }        printf("%d", getSum(1));        for(int i=2;i<=n;i++)        {             printf(" %d",getSum(i));        }        printf("\n");    }    return 0;}

HDU 1166 敌兵布阵  此题还有线段树解法

#include <cstdio>#include <cstring>#define INF 0x7FFFFFFF#define FOR( i , a , b ) for ( int i = a ; i <= b ; ++ i )#define CLR( a , x ) memset ( a , x , sizeof (a) )#define RE freopen("1.in","r",stdin)#define LL(i) (i<<1)#define RR(i) (i<<1|1)#define MAX 50010int ans;int a[MAX];int lowbit(int x){    return x&(-x);}int sum(int i){    int ans = 0;    while(i > 0)    {        ans += a[i];        i -= lowbit(i);    }    return ans;}void add(int i,int num){    while(i <= MAX)    {        a[i] += num;        i += lowbit(i);    }}int main(){    RE;    int n,m,l,r,t,x;    char cmd[10];    scanf("%d",&t);    FOR(tt,1,t)    {        CLR(a,0);        printf("Case %d:\n",tt);        scanf("%d",&n);        FOR(i,1,n)        {            scanf("%d",&x);            add(i,x);        }        getchar();        while(scanf("%s",cmd),cmd[0]!='E')        {            switch(cmd[0])            {                case 'Q':                        scanf("%d%d",&l,&r);                        ans = sum(r) - sum(l-1);                        printf("%d\n",ans);                        break;                case 'A':                        scanf("%d%d",&l,&r);                        add(l,r);                        break;                case 'S':                        scanf("%d%d",&l,&r);                        add(l,-r);                        break;            }        }    }return 0;}






0 0