树状数组学习小结

来源:互联网 发布:js获取元素的高度 编辑:程序博客网 时间:2024/05/01 02:30

树状数组,又称二进制索引树,英文名Binary Indexed Tree。

一、树状数组的用途

主要用来求解数列的前缀和,a[0]+a[1]+...+a[n]。

由此引申出三类比较常见问题:

1、单点更新,区间求值。(HDU1166)

2、区间更新,单点求值。(HDU1556)

3、求逆序对。(HDU2838)

 

二、树状数组的表示

1、公式表示

设A[]为一个已知的数列。C[]为树状数组。则会有

C[i]=A[j]+...+A[i];j=i&(-i)=i&(i^(i-1))。

2、图形表示

(注:1、最下面的一行表示数组A,上面的二进制表示的部分是C;

2、图片来源于http://hi.baidu.com/rain_bow_joy/blog/item/569ec380c39730d2bc3e1eae.html)

 

从以上可以发现:

1、树状数组C是表示普通数组A的一部分的和。

2、小标为奇数时,C[i]只能管辖一个A[i]。

3、C[i]的最后一个数一定是A[i]。

 

三、树状数组的关键代码

1、

int lowBit(int x){    return x&(-x);}

这段代码可以简单的理解为是树状数组向前或向后衍生是用的。

向后主要是为了找到目前节点的父节点,比如要将C[4]+1,那么4+(4&(-4))=8,C[8]+1,8+(8&(-8))=16,

C[16]+1。

向前主要是为了求前缀和,比如要求A[1]+...+A[12]。那么,C[12]=A[9]+...+A[12];然后12-12&(-12)=8,

C[8]=A[1]+...+A[8]。

 

2、

void modify(int pos,int num)  //pos为数组下标位置,num为要增加的值 {    while(pos<=n)   //n为数组的长度     {        c[pos]+=num;        pos+=lowBit(pos);    }}

这段代码是用来更新树状数组的,包括区间更新、单点更新。

就是想刚才所说的,一点更新了,要不断将父节点也更新。

 

3、

int getResult(int pos)  //求A[1]+...+A[pos] {    int sum=0;     while(pos>0)    {        sum+=c[pos];        pos-=lowBit(pos);    }        return sum; }                

这段代码用来求解前缀和的。

就像刚才说的,求解A[1]+...+A[12],也就是C[12]+C[8]搞定。

 

四、树状数组的优点

1、原本的长度为n的数列求和时间复杂度为O(n),更改的时间复杂度为O(1)。

树状数组将其优化为O(logn)。在n较大时,效率更高。

2、树状数组编码简单。

 

五、注意

1、树状数组的下标要从1开始。

2、在学习的过程中遇到这么个问题。不知道为什么pos+pos&(-pos)就到了pos的父节点,也不知道

为什么pos-pos&(-pos)就得到了下一个无联系的节点,从而可以得到前缀和。

我只能说:我不懂如何证明,这是数学问题了,树状数组的发明者应该就是发现了这点才搞出树状

数组的吧。初学者不妨抛开这点,专注于事实,将上面的图形自己计算画一遍,非常有利于理解。

 

六、符代码:

HDU1166

单点更新,区间求值

#include<iostream>using namespace std;const int maxn=50001;int a[maxn];int c[maxn];int n; int lowBit(int t){    return t&(-t);}void modify(int t,int num){    while(t<=n)    {        c[t]+=num;        t+=lowBit(t);    }}int getResult(int t){    int num=0;     while(t>0)    {        num+=c[t];        t-=lowBit(t);    }        return num; }void init(){    for(int i=1;i<=n;i++)    {        scanf("%d",&a[i]);                modify(i,a[i]);     }}int main(){    int cas,Case=1;        scanf("%d",&cas);     while(cas--)    {         memset(c,0,sizeof(c));         printf("Case %d:\n",Case++);                 scanf("%d",&n);                init();                char ch[15];        int a,b;           while(scanf("%s",&ch),strcmp(ch,"End"))        {             scanf("%d%d",&a,&b);                         switch(ch[0])            {                case 'Q':                    printf("%d\n",getResult(b)-getResult(a-1));                    break;                 case 'A':                     modify(a,b);                    break;                case 'S':                    modify(a,-b);                    break;            }         }     }          system("pause");     return 0;} 

 

HDU1556

区间更新,单点求值

#include<iostream>#include<cstring>using namespace std;const int maxn=100001;int c[maxn];int n;int lowbit(int t){    return t&(-t);}void insert(int t,int d){    while(t<=n)    {        c[t]+=d;        t+=lowbit(t);    }}int getSum(int t){    int sum=0;    while(t>0)    {        sum+=c[t];        t-=lowbit(t);    }        return sum;}int main(){    while(cin>>n,n)    {        int a,b;        memset(c,0,sizeof(c));                for(int i=1;i<=n;i++)        {            scanf("%d%d",&a,&b);                        insert(a,1);            insert(b+1,-1);        }               for(int j=1;j<n;j++)       {            printf("%d ",getSum(j));       }       printf("%d\n",getSum(n));    }        system("pause");    return 0;}

 

HDU2838

求逆序对

#include<iostream>#include<cstring>using namespace std;const int maxn=100001; struct node{    int cnt;    __int64 sum;}tree[maxn];                 int n;int lowBit(int x){    return x&(-x);}void modify(int x,int y,int t){    while(x<=n)    {        tree[x].sum+=y;        tree[x].cnt+=t;  //tree[].cnt来保存是否出现过a         x+=lowBit(x);    }}__int64 query_cnt(int x)   //比x小的数的个数 {    __int64 sum=0;    while(x>0)    {        sum+=tree[x].cnt;        x-=lowBit(x);    }        return sum;}__int64 query_sum(int x)  //比x小的所有数之和 {    __int64 sum=0;    while(x>0)    {        sum+=tree[x].sum;        x-=lowBit(x);    }        return sum;}int main(){    while(~scanf("%d",&n))    {        int a;        __int64 ans=0;         memset(tree,0,sizeof(tree));                 for(int i=1;i<=n;i++)        {            scanf("%d",&a);                        modify(a,a,1);  //以a为下标更新数组                         __int64 k1=i-query_cnt(a);   //k1为前i个数比a大的数的个数             if(k1!=0)            {                __int64 k2=query_sum(n)-query_sum(a); //目前所有数的和-目前所有比a小的数的和,为比a大的数的和                   ans+=k1*a+k2;   //调换a所需的时间             }        }                printf("%I64d\n",ans);     }        system("pause");    return 0;} 

 

七、二维树状数组

C[x][y]=sum(A[i][j])。其中,x-lowBit(x)+1<=i<=x,y-lowBit(y)+1<=j<=y。

例题:HDU1892

二维树状数组一般就是对矩阵的操作,更新、求值。。。

代码:

#include<iostream>#include<cstring>using namespace std;const int maxn=1005;int c[maxn][maxn];int lowBit(int x){    return x&(-x);}void modify(int x,int y,int val){    for(int i=x;i<maxn;i+=lowBit(i))    {        for(int j=y;j<maxn;j+=lowBit(j))        {            c[i][j]+=val;        }    }}int getResult(int x,int y){    int sum=0;    for(int i=x;i>0;i-=lowBit(i))    {        for(int j=y;j>0;j-=lowBit(j))        {            sum+=c[i][j];        }    }        return sum;}int getVal(int x,int y){    return getResult(x,y)-getResult(x-1,y)-getResult(x,y-1)+getResult(x-1,y-1);}void init(){    memset(c,0,sizeof(c));        for(int i=1;i<maxn;i++)    {        for(int j=1;j<maxn;j++)        {            modify(i,j,1);        }    }}    int main(){    int cas,cas1=1,query;        scanf("%d",&cas);    while(cas--)    {        init();                scanf("%d",&query);        printf("Case %d:\n",cas1++);        for(int i=1;i<=query;i++)        {            char ch;            int x1,y1,x2,y2,n;                        getchar();            scanf("%c",&ch);                        switch(ch)            {                case 'S':                    {                    scanf("%d%d%d%d",&x1,&y1,&x2,&y2);                    int x11=min(x1,x2);                    int x22=max(x1,x2);                    int y11=min(y1,y2);                    int y22=max(y1,y2);                    printf("%d\n",getResult(x22+1,y22+1)-getResult(x11,y22+1)-getResult(x22+1,y11)+getResult(x11,y11));                    break;                    }                case 'A':                    {                    scanf("%d%d%d",&x1,&y1,&n);                    modify(x1+1,y1+1,n);                    break;                    }                case 'M':                    {                    scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&n);                    int v=getVal(x1+1,y1+1);                    int Min=min(n,v);                    modify(x1+1,y1+1,-Min);                    modify(x2+1,y2+1,Min);                    break;                    }                case 'D':                    {                    scanf("%d%d%d",&x1,&y1,&n);                    int v=getVal(x1+1,y1+1);                    int Min=min(v,n);                    modify(x1+1,y1+1,-Min);                    break;                    }            }        }    }        system("pause");    return 0;}


 

八、参考文章
http://mindlee.net/2011/07/10/binary-indexed-trees/ 

http://dongxicheng.org/structure/binary_indexed_tree/

原创粉丝点击