此为算法速成blog,因此会略去一些推导细节。
简介:
树状数组是一个**查询和插入时间复杂度都在O(logn)**的数据结构。
应用:
主要用于查询区间和或者前n项和,但**每次只能修改一个元素的值。**经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值(如果加入多个辅助数组则可以实现区间修改与区间查询)。
概念:
来观察这个图:
令这棵树的结点编号为C1,C2…Cn. Ci的值为子树叶子结点的权值总和,那么容易发现:
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
…
为什么要这样设计这棵树呢?答:这样设计可以利用二分来操作。
以下是百度百科的解释,由于我们是速成,可以暂时不用去理解。
答案是,这样会使操作更简单!看到这相信有些人就有些感觉了,为什么复杂度被log了呢?可以看到,C8可以看作A1~A8的左半边和+右半边和,而其中左半边和是确定的C4,右半边其实也是同样的规则把A5~A8一分为二……继续下去都是一分为二直到不能分树状数组巧妙地利用了二分,树状数组并不神秘,关键是巧妙!
那么现在,我们来通过A求C.
这里给出一条公式: C[i]=A[i-2k+1]+A[i-2k+2]+…A[i] k为i二进制位的末尾0长度, i >= 1。
这个公式什么意思呢?它其实是说明了C[i]的管辖范围在[i-2k+1 , i ]。
比如说,i = 8,二进制为1000,k = 3,2k= 8,那么c[8]就是A[1]…A[8]的权值总和。
i = 7,二进制为111,k = 0, 2k = 0,那么c[7]就是A[7].
下面给出计算2k的方法。
int lowbit(int x)
{
return x & -x;
}
至于为什么是这样,我现在也不太清楚。大家可以查看其他的blog. 我们现在是速成,这个公式直接记住就可以。
有了上面的函数,我们的公式可以改写成C[i] = A[i-lowbit(i)+1] + A[i-lowbit(i)+2] +…+A[i];
也就是C[i]的管辖范围在[ i-lowbit(i)-1, i ].
ok,现在你已经知道C[i]怎么算了。那么回到应用,如何利用C[i]计算区间和或者前n项和呢?下面先给出代码。
int sum(int k) //前k个数的和
{
int ans = 0;
while(k)
{
ans += t[k];
k -= lowbit(k);
}
return ans;
}
拿k = 6举例子,C4 = A1 + A2 + A3 + A4, C6 = A5 + A6,那么sum = C4 + C6;
i = 6,lowbit = 2;i = 4,lowbit = 4;
因此可以发现,传进去的参数k表示从1到k的整个区域,lowbit(i)表示C[i]的管辖范围,实际上是将一段段的C全部加起来。
那么如何求区间和呢?下面直接上代码。
int ask(int l,int r) //求l ~ r区间和
{
return sum(r) - sum(l-1);
}
其实就是后面的前n项和与前面的前n项和相减,就可以得到中间的区间和了嘛。
那么当改变一个A[i]的值之后,怎么更新C[i]呢?
void change(int x,int p)//将第x个数加p
{
while(x <= n)
{
t[x] += p;
x += lowbit(x);
}
return;
}
从第x个数开始,当前的C[x]加上p,然后跳到下一个与A[x]有关的C (x + lowbit(x)),也加上p. 实际上就是将范围扩大,把与A[x]有关的C全部都加上p.
总代码:
#include <iostream>
#define MAX 10010
using namespace std;
int t[MAX], a[MAX];
int n;
int lowbit(int x) //求C管辖范围 2^k
{
return x & -x;
}
int sum(int k) //前k个数的和
{
int ans = 0;
while(k)
{
ans += t[k]; //将每段C都加起来。
k -= lowbit(k); //减去C[k]的管辖区域
}
return ans;
}
int ask(int l,int r) //求l ~ r区间和
{
return sum(r) - sum(l-1);
}
void change(int x,int p)//将第x个数加p
{
while(x <= n)
{
t[x] += p;
x += lowbit(x);
}
return;
}
int main()
{
cin>>n;
for(int i = 1;i <= n;i++)
{
a[i] = i;
change(a[i], i);
}
int l ,r;
while(cin>>l>>r)
{
cout<<ask(l,r)<<endl;
}
return 0;
}
相关资料:
百度百科“树状数组”