树状数组算法

有一道题
数组a下标为0-n-1,现在给你q次操作,q次操作若1打头则为修改,2则是查询,修改是修改a数组某下标的一个值,查询的话是查询区间和,q<500000
输入格式为
第一行一个整数n
第二行n个整数a[i]
第三行一个整数q
之后的q行每行三个整数
输出格式为查询结果

样例输入
7
5 4 3 2 1 2 3
4
1 0 3
2 0 3
1 0 5
2 0 4
样例输出
12
14

这道题目有朴素做法,朴素做法如下:
思路很简单,修改就是修改
然后区间和就是遍历,时间复杂度冲上了O(nq)以上
所以直接TLE没商量

#include<bits/stdc++.h>
using namespace std;
int main(){
	int a[100001], n, q;
	cin >> n;
	for(int i = 0 ; i < n ; i ++)
	{
		cin >> a[i];
	}
	cin >> q;
	for(int i = 0 ; i < q ; i ++)
	{
		char op;
		cin >> op;
		if(op == '1')
		{
			int a_, b;
			cin >> a_ >> b;
			a[a_] = b;
		}else
		{
			int l, r;
			cin >> l >> r;
			int sum = 0;
			for(int i = l ; i <= r ; i ++)
			{
				sum += a[i];
			}
			cout << sum << endl;
		}
	}
	return 0;
}

然后又有人说:我学过前缀和,可以拿前缀和优化
我:我前缀和掌握的不好,不写代码了,虽然查询是O(1),但是很抱歉每改一次数据会有很大的时间复杂度去更新,编程了O(nw),(w指修改数值次数),所以也是TLE。

那就没有别的方法了吗?

树状数组你不爱,非要在暴力身上找存在

树状数组铺垫——lowbit函数

lowbit函数的用处

这是树状数组的核心
我的提高组编程老师说:他第一次学习这个算法的时候,是颇为震撼的,不知道作者是在什么样的精神状态下创作出的如此离谱的算法

lowbit函数的具体功能

lowbit函数可以去除传入的值(n)的二进制下的高位1,只保留一个最低位的1
比如10的二进制是1010 lowbit(10) = lowbit(1010) = 0010,将千位的1去除
那么要如何实现这个函数呢?
我们举个例子,比如一个二进制数为1111110,对它执行lowbit函数,目标就是将它变成0000010
如果我们要求k的lowbit函数的话,就是k&-k
我们都知道在一个数前面加个减号就是这个数字的二进制取反+1
-10的二进制是1010 = 0101 + 1 = 0110
&符号就是要对两个数字的二进制进行&&运算(即若两数都为1则为1,否则为0)
1010 & 0110 = 0010
这个东西可能很难理解,那我们再来解释一下
首先不可能按位取反,因为这样0变1,1变0,去和原数做&运算一定是0,而加1可以产生一个最低位0
——————————————这个东西十分的微妙awa
你要是还是不信那就再练习两年半再回来看这个东西,但是结果是对的,不信你写
(备注:lowbit没有头文件你得自己写)

#include<iostream>
using namespace std;
int lowbit(int x)
{
    return x&(-x);
}
int main()
{
	int n;
	cin >> n;
	cout<<lowbit(n);
	return 0;
}

输入10,输出是啥?2!
这很正常啊
lowbit(n)的二进制是0010
0010是多少?
是2啊!
那就说明lowbit函数是没有问题的

树状数组

树状数组,重点是树状
二叉树大家都不陌生了
比如说有一个8个节点的二叉树
像这样
叶子节点代表A[1] ~ a[8]
但是树状数组做了一些变形
这个创作者把所有非叶子节点往右边扯
变成了这副模样
awa
这已经非常丑了,但是创作者十分的丧心病狂
他给每一列的顶端标了个值
在这里插入图片描述
然后可能有大聪明就要问了:“啊,前面不是a嘛”
对了,它是A啊,所以你们再看这张图
在这里插入图片描述
这下是不是更加懵了,好了现在我来介绍A和C的关系
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]
看完关系是不是直接傻掉了
这哪到哪啊,根本没有规律可以言啊
但是仔细看这张图片,你就会发现,C[8]的所有儿子就是A[1] ~ A[8]
C[1],C[3],C[5],C[7]是叶子节点所以只有一个值
而C[2]是C[1]的爸爸啊,所以就是A[1] + A[2]
然后C[4]是C[1]~C[3]的爸爸,所以就是A[1] + A[2] + A[3] +A[4]
C[6]是C[5]的爸爸所以是A[5] + A[6]
C[8]是整个树的根节点啊,所以是A[1] + A[2] + A[3] + A[4] + A[5] + A[6] + A[7] + A[8]
现在更加混乱了是不是

归纳总结

就算我们知道了C[1] ~ C[8]代表了什么,可是C[100]是多少呢,这才是当务之急啊
其实这个式子很复杂 C[i] = A[i-2^k+1] + A[i-2^k+2]+…A[i]

k是什么东西

k是i的二进制中从最低位到最高位连续0的长度
先以8为例吧,8的二进制是1000,那么k为3,为什么,因为末尾连续0长度为3啊
C[8] = A[8-23+1] + A[8-23+2]…A[8]
那么这个东西很管用啊,最后推导得知
8-23+1 = 1
8-23+2 = 2
8-23+3 = 3
8-23+4 = 4
8-23+5 = 5
8-23+6 = 6
8-23+7 = 7
8-23+8 = 8
那7呢
7 = 111
111连续0长度为0
7-20+1
20=1
7-1+1 = 7
然后我在上课的时候和各位一样想到了一个问题,连续0?其实不是,是总共0的数量
现在再想想是不是感觉和lowbit函数搭边了?

你清空了高位所有1了以后
只剩下一个1了,就可以统计了
若为4
二进制100
去除所有最高位0(没有了awa
2个0

区间查询

若我们要查询一段区间的和
倘若查询3-7
那么就是
7の前缀和-2(3-1,对你没看错)の前缀和
接下来我们就需要处理前缀和(用树状数组)
根据上面的表我们可以得到
sum[7] = C[4]+C[6]+C[7]
将C[]数组的节点序号转换为二进制
对于每一层而言,你肯定只选1个点
在这里插入图片描述
必须要保证区间连续
以第二层为例
若同时选了两个节点(那个点虽然是空气)那个点编个数字
首先4=A[1]+…A[4]
那个空气肯定要表达A[5]+…+A[8]
就相当于你去选根节点

代码部分

这个是求和の代码

int ans=0;
for(int i  = x ; i > 0 i -= lowbit(i))
	ans += C[I]
	return ans;

然后是修改の代码
这样就可以全部进行修改了
将编号为x的点+y

void change(int x, int y){
	for(int i = x ; i <= n ; i += lowbit(i)) tree[i] += y;
}

例题P3374
点修改,段查询问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值