树状数组

树状数组

一维树状数组

​ 首先,我们现在需要维护一个一维数组的前缀和,设\(ans[i] = arr[1]+arr[2]+\cdots+arr[i]\)。现在如果我们要修改任意一个元素\(arr[i]\)的值,则相关的前缀和\(ans[i],ans[i+1],\cdots,ans[n]\)都会发生变化。如果我们采用传统的线性顺序扫描求连续数组元素的和,则每次修改\(arr[i]\)后,修整前缀和\(ans[]\)的时间复杂度为\(O(N)\)。若修改数组\(arr[]\)中的元素的次数是\(m\),则整体前缀和的时间复杂度是\(O(N*M)\)

​ 为了更好的维护前缀和的效率,这里我们可以是使用树状数组。树状数组通过将线性结构转换为树状结构(线性的只能逐个扫描元素,而树状的可以实现跳跃式的扫面),使得修改数组元素值后,维护前缀和的时间复杂度为\(O(log_2n)\),大大的提高了整体的效率。

原理

下面让我们通过一张图来了解树状数组的原理:

注意:图中的子节点包括自己,例如节点\(8\),这里面的值是原始数组中\([5,8]\)的和,图中灰色的节点实际上是已被上层覆盖,不再用空间。

下面我们看二进制版本的图:

通过第二张图我们可以发现:
更新过程是每次加上二进制中的低位\((101+1\rightarrow 110,110+10\rightarrow 1000 1000+1000\rightarrow 10000)\)
查询过程是每次减去二进制中的低位\((1111-1\rightarrow 1110,1110-10\rightarrow 1100,1100-100\rightarrow 1000)\)

那么现在我们怎样来实现这个功能呐?(即怎样寻找一个数的二进制表示中的最低的一位\(1\)),所以我们要在这里了解一个叫做lowbit的函数,顾名思义,lowbit这个函数的功能就是求某一个数的二进制表示中最低的一位1

具体操作为:

int lowbit(int x){return x & (-x);} 

看着是不是极其简单!!!现在让我们来了解这行代码:

我们知道,对于一个数的负数就等于对这个数取反\(+1\)。我们可以以二进制数\(101\)为例,\(101\)的补码为\(010\),再加上\(1\)就等于\(011\),最后\(101\)\(011\)在进行与运算就等于最低位的\(1\)

其实这个很好理解,原码与补码必然是相反的,负数实在补码的基础上加\(1\),那么由于进位,负数最末尾的\(1\)和原码最右边的\(1\)一定会在同一个位置。

了解了lowbit函数和树状数组的操作原理之后,我们就可以进行区间查询和单点修改了。

单点修改

现在让我们回到第一张图,如果现在我们要对\(arr[5]\)进行修改(加上\(d\)),则需要进行以下修改:
\(5(101),arr[5] += d\)
\(5+lowbit(5)=101+1=110=6,arr[6]+=d\)
\(6+lowbit(6)=110+10=1000=8,arr[8]+=d\)
\(8+lowbit(8)=1000+1000=10000=16,arr[16]+=d\)

写成代码就是:

void updata(int x, int b){//x为更新的位置,b为更新后的数,n为数组最大值
	while(x <= n){
		arr[x] += b;
		x += lowbit(x);
	}
}

区间查询

其实区间查询就是单点修改的逆操作

例如查询区间\([1,5]\)\(arr[5]=A[5],arr[4]=A[1]+A[2]+A[3]+A[4]\)
所以\(getsum(5)=arr[101]+arr[100]\)\(5-lowbit(5)=101-1=100\)

写成代码就是:

int getsum(int x){
	ll res = 0;
	while(x > 0){
		res += arr[x];
		x -= lowbit(x);
	}
	return res;
}

二维树状数组

我们已经学会了对于以为树状数组的常规操作,那么我们好奇(谁没事,这么的无聊)能不能把类似的操作放在矩阵上呢?这里我们就来写二维的树状数组。

我们了解了一维树状数组的原理,二维树状数组和一维树状数组类似,在二维树状数组中,\(arr[x][y]\)记录的是右下角为\((x,y)\),高度为\(lowbit(x)\),宽度为\(lowbit(y)\)的区间和。

单点修改

void updata(int x, int y, int d){//将点(x, y)加上d
    int temp = y;
    for(; x <= n; x += lowbit(x)){//当x是固定值时,这就是一个横向的一维树状数组的操作
        for(y = temp; y <= n; y += lwbit(y)){//假设y是固定值时,这就是一个竖向的一维树状数组的操作
            arr[x][y] += d;
        }
    }
}

区间查询

int getsum(int x, int y){//求左上角为(1,1)右下角为(x,y) 的矩阵和
    int res = 0, temp = y;
    for(;x > 0; x -= lowbit(x)){
        for(y = temp; y > 0; y -= lowbit(y)){
            res += arr[x][y];
        }
    }
    return res;
}
posted @ 2021-05-06 12:01  h星宇  阅读(52)  评论(0编辑  收藏  举报