您的位置:首页 > 其它

树状数组学习小结

2014-11-21 20:49 429 查看
树状数组,又称二进制索引树,英文名Binary Indexed Tree。

一、树状数组的用途

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


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

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、

[cpp] view
plaincopyprint?

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、

[cpp] view
plaincopyprint?

void modify(int pos,int num) //pos为数组下标位置,num为要增加的值

{

while(pos<=n) //n为数组的长度

{

c[pos]+=num;

pos+=lowBit(pos);

}

}

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

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

3、

[cpp] view
plaincopyprint?

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

单点更新,区间求值

[cpp] view
plaincopyprint?

#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

区间更新,单点求值

[cpp] view
plaincopyprint?

#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

求逆序对

[cpp] view
plaincopyprint?

#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

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

代码:

[cpp] view
plaincopyprint?

#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/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: