说一下树状数组,和线段树一样,线段树和树状数组都是为了加快素组的操作效率的,那么,为什么要弄两个数据结构来达到一个目的呢?
所以先说一下线段树与树状数组的区别,线段树的功能强大,而树状数组的速度更快。不过树状数组的时间复杂度也是O(logN)但是树状数组的常数小。
然后我们说一下树状数组的样子。如图:
和线段树非常不同的是是树状数组的空间小,是多大就是多大,不像线段树一样要开4倍空间。
这个图看上去比较乱,说明一下,A数组就是我们平时说的数组,那么C数组就是它对应的数组。第一次看这个图我也很乱,但是,注意,所有的树状数组都是长一个样的,都是这么乱。。。说一下C数组吧,C数组的值就是他连出的线的值的合,如图二!
如图,我们可以看到:
C1 = A1
C2 = A1+A2
C3 = A3
C4 = A1+A2+A3+A4
C5 = A5
C6 = A5+A6
C7 = A7
C8 = A1+A2+A3+A4+A5+A6+A7+A8
很明显:
设节点编号为X,区间内有2^k个元素,(k为x转为二进制后末尾的0的个数,)
其实我觉得并不明显。。。
不需要管那个性质,我们只需要知道怎么用,用函数写:
int lowbit(int x) {
return x & -x;
}
这个比较简单好记
那么如何求和呢
直接给代码
int getsum(int x) {//目标:求和A1~Ax
int res = 0;
for (; x; x -= x & (-x))
res += t[x];
return res;
}
然后就是修改,与求和类似,不同的就是+=和-=
int change(int x) {
for (; x <= maxn; x += x & (-x))
t[x]++;
}
这里给出模板:
#include
using namespace std;
const int MAX_N=10010;
int C[MAX_N];
int n;
int lowbit(int x){
return x&(-x);
}
int getsum(int x){
int res=0;
for(;x;x-=lowbit(x)){
res+=C[x];
}
return res;
}
void change(int x,int c){
for(;x<=n;x+=x&(-x)){
C[x]+=c;
}
}
int main() {
cin>>n;
for(int i=1;i<=n;i++){
int d;
cin>>d;
change(i,d);
}
for(int i=1;i<=n;++i){
cout<" ";
}
return 0;
}
注意这份代码是单点更新与整体求和
如何求一个区间的和呢?
简单getsum(b)-getsum(a-1);
然后我们说一下线段树的区间更新和单点查询:
先说区间更新,这个需要用到差分的思想,直接抄了两位洛谷大犇的原话
来介绍一下差分
设数组a[]={1,6,8,5,10},那么差分数组b[]={1,5,2,-3,5}
也就是说b[i]=a[i]-a[i-1];(a[0]=0;),那么a[i]=b[1]+….+b[i];(这个很好证的)假如区间[2,4]都加上2的话,a数组变为a[]={1,8,10,7,10},b数组变为b={1,7,2,-3,3};
发现了没有,b数组只有b[2]和b[5]变了,因为区间[2,4]是同时加上2的,所以在区间内b[i]-b[i-1]是不变的.
所以对区间[x,y]进行修改,只用修改b[x]与b[y+1]:
b[x]=b[x]+k;b[y+1]=b[y+1]-k;
还有一个
这是树状数组的一个拓展,在树状数组中可以用前 i 项的和来表示第 i 个数.
那么当对 x ~ y 的区间进行修改的时候需要在树状数组中的第 x 个位置 + k, 第 y + 1 个位置 -k
这样便维护了这个树状数组
输出时候直接输出查询即可
注意这里的数组仅仅是一个差分的数组,就像整体在一部分抬高了一截,但是不管是抬高的那一段还是没抬高的,相对左右还是一样的,分界线除外。
而如果树状数组初始化为0这和差为了正好重合,相当于求两点差分和就是求两点区间和!
上代码:
#include
#include
using namespace std;
int MAX_N=10000;
int s[MAX_N];
int n;
void lowbit(x){
return x & (-x);
}
void change(int x, int v) {
for(;x<=n;x+=lowbit(x))tree[x]+=v;
}
int main() {
scanf("%d", &n);
int last = 0, now; //差分用
for (int i = 1; i <= n; i++) {
scanf("%lld", &now);
add(i, now - last);
last = now;
}
int x, y; //区间[x,y]+k
long long k;
scanf("%d%d%lld", &x, &y, &k);
add(x, k);
add(y + 1, -k);
return 0;
}
有了区间更新后单点查询与区间更新代码一起上
#include
#include
using namespace std;
int MAX_N=10000;
int s[MAX_N];
int n;
void lowbit(x){
return x & (-x);
}
void change(int x, int v) {
for(;x<=n;x+=lowbit(x))tree[x]+=v;
}
int query(int x) {
int res = 0;
for(;x;x-=lowbit(x)){
res+=s[x];
}
return res;
}
int main() {
//区间更新
scanf("%d", &n);
int last = 0, now; //差分用
for (int i = 1; i <= n; i++) {
scanf("%lld", &now);
add(i, now - last);
last = now;
}
int x, y; //区间[x,y]+k
long long k;
scanf("%d%d%lld", &x, &y, &k);
add(x, k);
add(y + 1, -k);
//单点查询
int x;
scanf("%d", &x);
printf("%d\n", query(x));
return 0;
}
一个由数字构成的大矩阵,能进行两种操作
1) 对矩阵里的某个数加上一个整数(可正可负)
2) 查询某个子矩阵里所有数字的和,要求对每次查询,输出结果。
一维树状数组很容易扩展到二维,在二维情况下:数组A[][]的树状数组定义为:
C[x][y] = ∑ a[i][j], 其中,
x-lowbit(x) + 1 <= i <= x,
y-lowbit(y) + 1 <= j <= y.
例:举个例子来看看C[][]的组成。
设原始二维数组为:
A[][]={{a11,a12,a13,a14,a15,a16,a17,a18,a19},
{a21,a22,a23,a24,a25,a26,a27,a28,a29},
{a31,a32,a33,a34,a35,a36,a37,a38,a39},
{a41,a42,a43,a44,a45,a46,a47,a48,a49}};
那么它对应的二维树状数组C[][]呢?
记:
B[1]={a11,a11+a12,a13,a11+a12+a13+a14,a15,a15+a16,…} 这是第一行的一维树状数组
B[2]={a21,a21+a22,a23,a21+a22+a23+a24,a25,a25+a26,…} 这是第二行的一维树状数组
B[3]={a31,a31+a32,a33,a31+a32+a33+a34,a35,a35+a36,…} 这是第三行的一维树状数组
B[4]={a41,a41+a42,a43,a41+a42+a43+a44,a45,a45+a46,…} 这是第四行的一维树状数组
那么:
C[1][1]=a11,C[1][2]=a11+a12,C[1][3]=a13,C[1][4]=a11+a12+a13+a14,c[1][5]=a15,C[1][6]=a15+a16,…
这是A[][]第一行的一维树状数组
C[2][1]=a11+a21,C[2][2]=a11+a12+a21+a22,C[2][3]=a13+a23,C[2][4]=a11+a12+a13+a14+a21+a22+a23+a24,
C[2][5]=a15+a25,C[2][6]=a15+a16+a25+a26,…
这是A[][]数组第一行与第二行相加后的树状数组
C[3][1]=a31,C[3][2]=a31+a32,C[3][3]=a33,C[3][4]=a31+a32+a33+a34,C[3][5]=a35,C[3][6]=a35+a36,…
这是A[][]第三行的一维树状数组
C[4][1]=a11+a21+a31+a41,C[4][2]=a11+a12+a21+a22+a31+a32+a41+a42,C[4][3]=a13+a23+a33+a43,…
这是A[][]数组第一行+第二行+第三行+第四行后的树状数组
给出模板代码:
#include
using namespace std;
const int MAX_N=810;
int C[MAX_N][MAX_N];
int n;
int lowbit(int x){
return x&(-x);
}
void change(int i,int j,int delta){
for(int x=i;x<=n;x+=lowbit(x)){
for(int y=j;y<=n;y+=lowbit(y)){
C[x][y]+=delta;
}
}
}
int getsum(int i,int j){
int res=0;
for(int x=i;x;x-=lowbit(x)){
for(int y=j;y;y-=lowbit(y)){
res+=C[x][y];
}
}
return res;
}
int main() {
cin>>n;
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
int d;
cin>>d;
change(i,j,d);
}
}
int x,y;
cin>>x>>y;
cout<return 0;
}
直接给代码
修改
void change(int r) {
c[r] = a[r];
for(int i = 1; i < lowbit(r); i <<= 1)
c[r] = max(c[r], c[r-i]);
}
查找
int getmax(int l, int r) {
int ret = a[r];
while(l <= r) {
ret = max(ret, a[r]);
for(--r; r - l >= lowbit(r); r -= lowbit(r))
ret = max(ret, c[r]);
}
return ret;
}
注意:它只支持末端插入,不支持单点修改操作。