1、树状数组是一种展开后类似树的一种数组,原理如下:
对于正整数x,我们可以将x进行二进制分解,,由此,我们可以将区间[1,x]划分为O(logx)个区间:
这些区间共同的特点是,若区间结尾为R,则区间长度就等于R的二进制分解下最小次幂,即为lowbit(R),例如7=2^2+2^1+2^0,区间[1,7]可以分为[7,7]、[5,6]、[1,4],长度分别是lowbit(7)=1、lowbit(7-lowbit(7))=2、lowbit(6-lowbit(6))=4,因此,只要给定x,我就可以算出[1,x]所分成的O(logx)个小区间
由此,对于给定的一个数组a,我们建立一个数组c,其中c[x]保存序列a的区间[x-lowbit(x)+1,x]中所有数的和,对于这个数组c,可以看成一个树形结构,该结构满足如下性质:
(1)、每个内部节点c[x]保存以它为根的子树中所有叶节点的和
(2)、每个内部节点c[x]的子节点个数等于lowbit(x)的位数
(3)、除根节点外,每个内部节点c[x]的父节点是c[x+lowbit(x)]
(4)、树的深度为O(logn)
树状数组支持的基本操作有两个,一个是单点修改,另一个是查询区间和
2、区间查询,单点修改,可以直接用原数组进行树状数组维护
//区间查询
int a[maxn], c[maxn];
ll lowbit(ll x)
{
return x & (-x);
}
ll getsum(int x) //获取前缀和
{
ll sum = 0;
for (; x; x -= lowbit(x)) //遍历c[x]划分成的每个小区间
sum += c[x];
return sum;
}
void add(int x, int y)
{
for (; x <= n; x += lowbit(x)) //由叶子节点不断向上
c[x] += y;
}
add(i, x); //i位置加上x
3、树状数组+差分数组:如果需要区间修改,那么则需要用树状数组维护差分数组,因为当对区间值进行加减时,对差分数组只有两端的数在改变
//树状数组+差分数组
ll b[maxn], c[maxn], n;
ll lowbit(ll x)
{
return x & (-x);
}
void addone(int x, ll y) //x下标,y数值
{
ll z = x * y;
for (; x <= n; x += lowbit(x))
{
c[x] += y;
b[x] += z; //如果需要区间查询(前缀和),需要额外维护一个x*c[x]的树状数组
}
}
void addall(int l, int r, ll y)
{
addone(l, y);
addone(r + 1, -y);
}
ll getsum(int x) //前缀和
{
ll sum1 = 0, sum2 = 0;
for (; x; x -= lowbit(x))
{
sum1 += c[x];
sum2 += b[x];
}
return sum1 * (x + 1) - sum2;
}
ll query(int l, int r) //求指定区间和
{
return getsum(r) - getsum(l - 1);
}
void solve()
{
int m;
ll x, y = 0;
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> x;
addone(i, x - y);
y = x;
}
addall(l, r, x); //区间修改
cout << query(l, r) << '\n'; //区间查询
}