树状数组学习小结
来源:互联网 发布: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/
- 树状数组学习小结
- 树状数组学习小结
- 树状数组学习小结
- 树状数组学习小结
- 树状数组的学习小结
- 树状数组的学习小结
- 树状数组小结
- 树状数组小结
- 树状数组小结
- 树状数组小结
- 树状数组小结
- 树状数组小结
- 树状数组小结
- 树状数组 小结
- 树状数组小结
- 树状数组小结
- 树状数组小结
- 树状数组小结
- STL中map用法详解
- SlimXml和TinyXml,RapidXml的性能对比
- 程序员屌丝的出路在哪?职业素养与契约精神
- VC++ ADO编程入门简介
- 从创业失败的阵痛中得到的宝贵经验(风车网的创业失败教训总结)
- 树状数组学习小结
- 一道SQL查询语句练习题
- Tomcat7.0 配置数据源
- poj 1276 Cash Machine
- poj1364-还是建模
- WebDriver入门及提高
- Tracking Code
- 对SQL事务隔离级别的简单理解
- Intent有关的作用用法