我们知道要是一个数组的和C[i]=A[1]+A[2]+.......+A[i],那么我更改其中任意一项值A[i],那么C[i],C[i+1],C[i+2]....C[n]都会随之变化,所以我们要调整C[]当n非常大时就需要很长的时间,必然会导致超时。这样就可以引入树状数组,它的修改和求和的复杂度为nlogn.
树状数组是一种数组结构,能够高效地获取数组中连续n个数的和。
下面这张图是基本上大多数博客都有的一张图,主要为了让我们更容易理解树状数组。
红色的C[i]表示树状数组,且C[i] = A[i–2^k+ 1] +A[i-2^k+2] … + A[i] (i>=1)
其中,k为i在二进制下末尾0的个数,(可以利用位运算2^k=i&(i^(i-1))=i&(-i)),则我们称C为树状数组。由树状数组的定义可知:
C[1]=A[1]; C[2]=A[1]+A[2]=C[1]+A[2]; C[3]=A[3]; C[4]=A[1]+A[2]+A[3]+A[4]=C[2]+C[3]+A[4];
C[5]=A[5]; C[6]=A[5]+A[6]=C[5]+C[6]; C[7]=A[7]; C[8]=...=C[4]+C[6]+C[7]+A[8];
由图可知,k为这颗树的高度,且最高不会超过logn,这个时候我们改变A[i]的值后可以由C[i]往根节点上溯,调整这条路上的C[]即可。比方说改变A[1],即pos=1的值,需要更新的值为C[1],C[2],C[4],C[8],位置变化的规律为:pos+=LowBit(pos),复杂度为logn。模板为:
- #define LowBit(num) num&(-num)
- void Add(int pos,int value)
- { for(int i=pos;i<MAX;i+=LowBit(i))
- C[i]+=value;
- }
要求统计某一个位置num的前num项和时:只要找到num前的所有的最大子树,然后把其根节点的C[]加起来即可。如:要求前5项的和,用Sum(5)表示,则Sum(5)=C[5]+C[4],需要加起来的C[i]中的i也有规律可循,i-=LowBit(i),模板为:
- int Add(int num)
- { int sum=0;
- for(int i=num;i>0;i-=LowBit(i))
- sum+=C[i];
- return sum;
- }
树状数组能快速求任意区间的和:A[i] + A[i+1] + … + A[j],设sum(k) = A[1]+A[2]+…+A[k],则A[i] + A[i+1] + … + A[j] = sum(j)-sum(i-1)。
用法1:树状数组---插点问线,当只有某一点的数值变化,而查找的区间为一段时用这个方法。
直接上题目吧,例题1:HDU 1166(敌兵布阵)直接用暴力的话肯定会超时。
- #include<iostream>
- #include<cstdio>
- #include<cstring>
- using namespace std;
- const int MAX=1000010;
- #define CLR(arr,val) memset(arr,val,sizeof(arr))
- int n,total,value,C[MAX];
- char Find[10];
- int LowBit(int num)//二进制后面为0的个数
- { return num&(-num);
- }
- void Add(int pos,int val)//将pos位置的值+val
- { while(pos<=n)
- { C[pos]+=val;//则每个位置的C[]都要改变,看下图有哪些C[]改变了,比方我把A[4]+val,变化的C[]有C[4],C[8]..变化,C[5],C[6]..并未变化
- pos+=LowBit(pos);//求出下一个要改变的位置,回溯到根节点
- }
- }
- int Sum(int num)//求前num项的和
- { int sum=0;
- while(num>0)
- { sum+=C[num];
- num-=LowBit(num);//找到num前的所有最大子树
- }
- return sum;
- }
- int main()
- { int sum=1;
- scanf("%d",&total);
- while(total--)
- { CLR(C,0);//要记得初始化
- printf("Case %d:\n",sum++);
- scanf("%d",&n);
- for(int i=1;i<=n;i++)//要从1开始!!!
- { scanf("%d",&value);
- Add(i,value);
- }
- while(scanf("%s",Find))
- { int End,Start;
- if(Find[0]=='E') break;
- scanf("%d%d",&Start,&End);
- if(Find[0]=='A') Add(Start,End);
- else if(Find[0]=='S') Add(Start,-End);
- else printf("%d\n",Sum(End)-Sum(Start-1));
- }
- }
- return 0;
- }
和这个题目一样的是:NYOJ 116(士兵杀敌)~~~~~~
用法2:插线问点,当有某一段的数值都有变化时,而查询只有一个位置时用插线问点。
- #include<iostream>
- #include<cstring>
- #include<cstdio>
- using namespace std;
- const int MAX=1000010;
- #define CLR(arr,val) memset(arr,val,sizeof(arr))
- int n,m,num,C[MAX];
- char Find[10];
- int LowBit(int num)
- { return num&(-num);
- }
- void Add(int pos,int val)//前n项每项增加val
- { while(pos>0)
- { C[pos]+=val;
- pos-=LowBit(pos);
- }
- }
- int Sum(int pos)
- { int sum=0;
- while(pos<=n)
- { sum+=C[pos];
- pos+=LowBit(pos);
- }
- return sum;
- }
- int main()
- { CLR(C,0);
- scanf("%d%d",&n,&m);
- for(int i=1;i<=n;i++)
- { scanf("%s",Find);
- if(Find[0]=='A')
- { int Start,End;
- scanf("%d%d%d",&Start,&End,&num);
- /****下面两行Nice****/
- Add(Start-1,-num);
- Add(End,num);
- /********************/
- }
- else
- { scanf("%d",&num);
- printf("%d\n",Sum(num));
- }
- }
- return 0;
- }
用法3:多维树状数组,增加模板:
- #define LowBit(num) num&(-num)
- void Add(int x,int y,....,int val)
- { for(int i=x;i<MAX;i+=LowBit(i))
- for(int j=y;j<MAX;j+=LowBit(j))
- .......//是几维的就几层
- C[i][j][...]+=val;
- }
- void Sum(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;
- }
例题3:HDU 1892(See you~~),代码:
- #include<iostream>
- #include<cstring>
- #include<cstdio>
- using namespace std;
- const int MAX=1010;
- #define min(a,b) a<b?a:b
- #define LowBit(num) num&(-num)
- #define CLR(arr,val) memset(arr,val,sizeof(arr))
- int total,n,Fx,Fy,Tx,Ty,value,sum,Count=1,C[MAX][MAX];
- void Add(int x,int y,int val)
- { for(int i=x;i<MAX;i+=LowBit(i))
- for(int j=y;j<MAX;j+=LowBit(j))
- C[i][j]+=val;
- }
- int Sum(int x,int y)//求从位置(1,1)到(x,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 main()
- { scanf("%d",&total);
- while(total--)
- { CLR(C,0);
- for(int i=1;i<MAX;i++)//记得要初始化为1
- for(int j=1;j<MAX;j++)
- Add(i,j,1);
- printf("Case %d:\n",Count++);
- scanf("%d",&n);
- for(int i=0;i<n;i++)
- { char c[5];
- scanf("%s",c);
- switch(c[0])
- { case 'A':
- scanf("%d%d%d",&Fx,&Fy,&value);//在(Fx,Fy)处增加value本书
- Add(Fx+1,Fy+1,value);
- break;
- case 'D':
- scanf("%d%d%d",&Fx,&Fy,&value);//在(Fx,Fy)处移除value本书,如果这里的书不足,则只移除原有的书
- sum=Sum(Fx+1,Fy+1)+Sum(Fx,Fy)-Sum(Fx+1,Fy)-Sum(Fx,Fy+1);//求出(Fx+1,Fy+1)位置的书的数目,可以用容斥定理解决
- value=min(value,sum);
- Add(Fx+1,Fy+1,-value);
- break;
- case 'M':
- scanf("%d%d%d%d%d",&Fx,&Fy,&Tx,&Ty,&value);//从(Fx,Fy)处移value本书到(Tx,Ty)处,同上面不足的话移除所有的书
- sum=Sum(Fx+1,Fy+1)+Sum(Fx,Fy)-Sum(Fx+1,Fy)-Sum(Fx,Fy+1);
- value=min(sum,value);
- Add(Fx+1,Fy+1,-value);
- Add(Tx+1,Ty+1,value);
- break;
- default:
- scanf("%d%d%d%d",&Fx,&Fy,&Tx,&Ty);
- if(Fx>Tx) swap(Fx,Tx);
- if(Fy>Ty) swap(Fy,Ty);
- sum=Sum(Tx+1,Ty+1)+Sum(Fx,Fy)-Sum(Fx,Ty+1)-Sum(Tx+1,Fy);
- printf("%d\n",sum);
- }
- }
- }
- return 0;
- }
求点(Fx+1,Fy+1)的书的数目:
可知:由面积关系可知,B点的值为:Sum(Fx+1,Fy+1)+Sum(Fx,Fy)-Sum(Fx+1,Fy)-Sum(Fx,Fy+1);
最后的求(Fx,Fy)到(Tx+1,Ty+1)的元素之和同样可求,即求ABCD的面积,只是C(Fx,Fy),B(Tx+1,Ty+1),A(Fx,Ty+1),D(,Tx+1,Fy),这个时候面积为:
Sum(Tx+1,Ty+1)+Sum(Fx,Fy)-Sum(Fx,Ty+1)-Sum(Tx+1,Fy)。
三维树状数组:关键是点的值的变化,如下图,只标了几个点的坐标,O不一定指原点,只是为了立体感强点。
例题4:HDU 3584(Cube)
- #include<iostream>
- #include<cstring>
- #include<cstdio>
- using namespace std;
- const int MAX=110;
- #define LowBit(num) num&(-num)
- #define CLR(arr,val) memset(arr,val,sizeof(arr))
- int n,m,C[MAX][MAX][MAX];
- void Add(int x,int y,int z,int val)
- { for(int i=x;i<=n;i+=LowBit(i))
- for(int j=y;j<=n;j+=LowBit(j))
- for(int k=z;k<=n;k+=LowBit(k))
- C[i][j][k]+=val;
- }
- int Sum(int x,int y,int z)
- { int sum=0;
- for(int i=x;i>0;i-=LowBit(i))
- for(int j=y;j>0;j-=LowBit(j))
- for(int k=z;k>0;k-=LowBit(k))
- sum+=C[i][j][k];
- return sum;
- }
- int main()
- { while(scanf("%d%d",&n,&m)!=EOF)
- { CLR(C,0);
- for(int i=0;i<m;i++)
- { int comp,Sx,Sy,Sz,Ex,Ey,Ez;
- scanf("%d",&comp);
- switch(comp)
- { case 0:
- scanf("%d%d%d",&Sx,&Sy,&Sz);
- printf("%d\n",Sum(Sx,Sy,Sz)&1);
- break;
- default:
- scanf("%d%d%d%d%d%d",&Sx,&Sy,&Sz,&Ex,&Ey,&Ez);
- //变成立方体了~~~~
- Add(Sx,Sy,Sz,1);
- Add(Sx,Ey+1,Ez+1,1);
- Add(Ex+1,Sy,Ez+1,1);
- Add(Ex+1,Ey+1,Sz,1);
- Add(Ex+1,Sy,Sz,1);
- Add(Sx,Ey+1,Sz,1);
- Add(Sx,Sy,Ez+1,1);
- Add(Ex+1,Ey+1,Ez+1,1);
- }
- }
- }
- return 0;
- }