树状数组学习小结

树状数组,又称二进制索引树,英文名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
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
#include
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

 

HDU2838

求逆序对

#include
#include
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
#include
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;i0;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


 

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

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

你可能感兴趣的:(Date,Structure)