初学树状数组(BIT)
菜鸟来学习树状数组了(耶)
还是从一个经典的问题引入吧
问题
对于一个长度为n的数列,有如下两种操作:
1.将元素ak加上x
2.查询任意一段区间的和。
(当然,两种操作都要进行多次)
在上面的问题中,其实只要能求到a1+a2+…的值,就可以解决问题了
为什么要有树状数组
(以下纯粹为个人意会,如有误,轻喷)
对于任意一个数字,我们都可以将其表示为2的幂次的和的形式,而且这种表示唯一。原因:任意一个十进制数都可以表示为唯一 一个二进制数。
比如,我们想求出a1+a2+…+a16
又想求出a1+a2+…+a19
常规的,我们一个一个加过去,时间复杂度为O(n),如果要做很多次这种操作,时间话费就很大
但是,我们知道,16=2^4 ,19=2^4 +2^1 +2^0
那么,假设我们知道a1+a2+…+a16、a17+a18、a19的值,并且存下来,那么,求和是不是会快很多?(logn级别的)
那你也许会问:为什么不是保存a1,a2+a3,a4+a5+…+a19呢?
因为这样没法统一啊。比如说这样你就没法求a1+a2+…+a16。
于是,lowbit和树状数组就诞生了。
lowbit函数
#define lowbit(x) ((x)&-(x))
(x是正整数)
这个函数实际上是寻找x的二进制表示中最低位的1出现的位置。(补码)
x | 1 2 3 4 5 6 7 8 9 |
---|---|
x的二进制 | 1 10 11 100 101 110 111 1000 1001 |
lowbit(x) | 1 2 1 4 1 2 1 8 1 |
tree[1]的值 | a1 |
---|---|
tree[2]的值 | a1+a2 |
tree[3]的值 | a3 |
tree[4]的值 | a1+a2+a3+a4 |
tree[5]的值 | a5 |
tree[6]的值 | a5+a6 |
tree[7]的值 | a7 |
tree[8]的值 | a1+a2+…+a8 |
这样安排tree数组是和lowbit相对应的(看tree中项的个数)。不得不说安排很巧妙。当然这只是编程技巧罢了。
void add(int x,int d)
{
while(x<=n)///n是数组中元素个数
{
tree[x]+=d;
x+=lowbit(x);
}
}
int sum(int x)
{
int ans=0;
while(x>0)
{
ans+=tree[x];
x-=lowbit(x);
}
return ans;
}
add
add函数用在初始化tree数组和更新tree中元素。最后代码其实是不用一个叫a[]的数组来存元素的,直接用tree存就好了。
比如说读入第三个数字为4(或者把第三个数字加四).调用add(3,4)
那么,tree[3]肯定被更新为4了(tree[3]+=4)
通过x+=lowbit(x),可以把所有含有a3项的tree数组内容都更新了(实在是妙啊)
sum
sum的思路就是十进制数表示成2的幂次的思路。具体不再详细说明
大抵难点如上。(剩下的就是刷题了。。。)