1.前言
正式开始讲解树状数组之前,我们先来思考一个问题,假如给你一个数组a[N],要你完成w次修改和q次查询区间和,这时你该怎么办?
如果说直接使用前缀和的话,那么查询操作时间复杂度为O(1),但是修改操作将变为O(n);但是如果不使用前缀和的话,那么查询操作将变为O(n),修改操作将变为O(1)。
那有没有一种方法能够将这两种操作兼顾呢,是有的。下面要讲解的树状数组就将这两种操作兼顾了,时间复杂度均为log(n),近乎完美的解决了上述问题。
2.lowbit函数
lowbit函数是树状数组中一个重要的辅助函数,用于计算一个数的最低位的1对应的值。具体来说,lowbit函数返回的是一个二进制数中最低位的1与其后面所有0构成的部分。
lowbit函数可以通过以下步骤来实现:
1. 对于给定的整数n,首先取其二进制的补码(补码是为了处理负数的情况,负数的补码表示是将其正数的二进制表示按位取反再加1)。
2. 对n取补码后,将其与n进行“与”运算,得到的结果就是最低位的1的值。
示例如下:
假设原始数n = 12,其二进制表示为1100,补码为1100。
则~n = 1100,补码为0100,与n = 1100进行“与”运算,得到的结果是0100。即最低位的1对应的值为4。
代码如下:
int lowbit(int x)
{
return x&(-x);
}
3.基本思想
树状数组的基本思想是基于二进制索引规则和位运算,通过一种精妙的方式来管理数组中的元素,从本质上说是将数组元素划分为不同的管理小组,每个管理小组管理一个或多个连续的元素。这种划分是基于二进制索引的,通过下标对数组中连续元素进行分组管理。每个管理小组中的元素都存储了原始数组中一部分元素的前缀和。
我们先来看下面这个著名的图:
通过看上面这个图,我们可以发现,数组中的元素被分成了一个个区间,具体如下:
我们给定输入的数组a[N],和另外一个数组c[N]。
c[1] = a[1],
c[2] = a[1] + a[2]
c[3] = a[3]
c[4] = a[1] + a[2] + a[3] + a[4]
c[5] = a[5]
c[6]=a[5]+a[6]
c[7]=a[7];
c[8]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8];
也就是说我们想找某一个数的前缀和,我们就需要这些区间的加和。
比如说 6 = 110 =100 + 10
那么s[6] = c[110] + c[110-lowbit(110)] = c[110] + c[100]
那s[7] = c[111] + c[110] + c[100]
顺势给出求和代码:
int query(int x)
{
int res=0;
for(int i=x;i>0;i-=lowbit(i))
res+=c[i];
return res;
}
看到这里,其实不免感叹设计者的精妙之处。
那么对于更新操作,其实根据上图我们也可以发现,如果我们要更新c[3],那么我们要接着更新 c[4],和c[8]。
而具体 3 = 11,那么每次更新操作其实都是加上lowbit(x)
即11 + lowbit(11)= 100 = c[4]
100 + lowbit(100)=1000 = c[8]
代码如下:
void add(int x,int k)
{
for(int i=x;i<=n;i+=lowbit(i))
c[i]+=k;
}
4.总结
下面给出模版题链接和代码,欢迎大家自行练习和交流提问
https://www.luogu.com.cn/problem/P3374
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=5e5+10;
int n,m;
int a[N];
int c[N];
int lowbit(int x)
{
return x&(-x);
}
void add(int x,int k)
{
for(int i=x;i<=n;i+=lowbit(i))
c[i]+=k;
}
int query(int x)
{
int res=0;
for(int i=x;i>0;i-=lowbit(i))
res+=c[i];
return res;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i];
add(i,a[i]);
}
for(int i=0;i<m;i++)
{
int flag;
cin>>flag;
if(flag==1)
{
int x,k;
cin>>x>>k;
add(x,k);
}
else
{
int x,y;
cin>>x>>y;
cout<<query(y)-query(x-1)<<endl;
}
}
return 0;
}