目录
1.为什么学习树状数组:
1.简介:对于我的认知我认为树状数组是解决一些区间问题和单点问题的工具,首先树状数组从名字上来看是将数组变成了一个树的结构,在这颗树上进行着区间和单点的某一些操作。
2.作用:此篇给出的树状数组的作用类似于前缀和。只不过和前缀和相比他提高了时间的 复杂度。
首先:前缀和是O(n)级别的,而树状数组是O(log(n))级别的。提高了时间的复杂度。其次:巧妙地利用了lowbit的属性,去降低了复杂度,而且把前缀和的各个位置遍历的极端位置给舍去了
2.简介lowbit()
如果读者对于位运算不知道的很详细的话,不用去深究;
lowbit:
1.作用是返回该数字在二进制下的第一位1和后面的0组成的数字
例如:
lowbit(1) 1的二进制是 1 那么结果就为1
lowbit(2) 2的二进制是10那么返回结果是10这二进制组成的十进制数字2
lowbit(3) 3的二进制是11根据定义他返回的就是第一位1和后面的0组成的数字1
那么如果二进制是101110000 返回的是10000这四位在二进制下构成的十进制
2.上代码:(代码记过就可以短而精悍)
int lowbit(int x) { return x & (-x); }
3.树状数组的精髓(!!!!重点掌握)
1.如果我们求一个区间(l,r)的和;
一.我们会想到可以从l加到r呀。问题400ms的时间TLE是肯定的
二.前缀和,非常完美可是前缀和还是可能超时(在数据很大的时候)。
三.线段树,高级的数据结构,代码很冗长,但是学会他树状数组问题可以利用他解决
四.树状数组,简洁和快。
2.树状数组的思想来源:(数字是这个数组的下标)
1.我们来想如果有以下的几个数组。
我们要求某个区间的和我们会怎么想?
那么我们会这样子两两相加,这样子我们节省了一个一个判断的一半的时间
那么我再一直相加变成这样子
那且不是更快。乍眼一看这不是是跟树差不多,可能把这个命名为树状数组!
2.那么开算吧
我们算一下几组:
(l=1,r=1)=[1];
(l=1,r=2) =[1+2]
(l=1,r=3) =[1+2]+[3]
(l=1,r=4) =[1+2+3+4]
(l=1,r=5) =[1+2+3+4]+[5]
(l=1,r=6) =[1+2+3+4]+[5+6]
(l=1,r=7) =[1+2+3+4]+[5+6]+[7]
(l=1,r=8) =[1+2+3+4+5+6+7+8]
呀!呀!呀!
你可能有疑问问我:你咋不算(l=5,r=6)呢?
1.我难道还要单独存吗,肯定不是,我在利用前缀和呀((l=5,r=6)= (l=1,r=6)-(l=1,r=4)
2.仔细看有些东西压根用不到呀,如果存前缀和的话!
观察我给出的几组样例每一行的偶数个都没有用呀,没用这不是占地方!删了!删了!
删完这样子了。那这样子删完接下来咋办呢,lowbit你教我了咋搞?
看图
我把每个数组的二进制代码和lowbit值写了出来你会发现lowbit(x)对应右边的第几层
接着看
1+lowbit(1)=2//第二层
2+lowbit(2)=4//第三层4+lowbit(4)=8//第四层
他会递层增加这样子我们加每一层就可
利用这个代码可以更新和赋值
void update(int x, int d) { //修改元素a[x], a[x] = a[x] + d while(x <= N) { tree[x] += d; x += lowbit(x); } }
那么为什么会用这个呢
那得看查询了
int sum(int x) { //返回前缀和sum = a[1] + a[2] +... + a[x] int ans = 0; while(x > 0){ ans += tree[x]; x -= lowbit(x); } return ans; }
比如查询7吧
那么ans+=t[7]
7变成了6
ans+=t[6]
6变成了4
ans+=t[4]
4变成了0
也逐层的减少
重要的是理解图片即可