树状数组

本文介绍了树状数组的概念,以及它与低bit(lowbit)函数的关系。树状数组能有效解决区间更新和求和问题,文章详细阐述了lowbit的两种计算方法,并通过举例说明了树状数组的操作,包括修改值和区间查询。此外,还讨论了树状数组在处理区间修改、单点查询、区间查询等问题的应用,以及如何利用离散化解决求逆序对数的问题。
摘要由CSDN通过智能技术生成

引入问题:

我们要输入一个序列a,要求在线修改一个值或者求一个子串的所有元素的和。

1.朴素算法(说白了就是暴力)

修改值:直接改,时间复杂度:O(1)
求字串的所有元素的和:一个循环,时间复杂度:O(n)
有那么有点慢。

2.前缀和

求字串的所有元素的和:一个计算:sun[i]-sum[i-1],时间复杂度:O(1)
修改值:有点麻烦了,要从 i 循环到 n,时间复杂度:O(n)
也有那么有点慢。

综上所述,好像差不了太多呀!

于是,我们的树状数组闪亮登场了!
但在了解树状数组之前,我们先了解一下lowbit();

lowbit()函数

lowbit()函数所求的就是最低位1的位置,所以可以通过位运算来计算 。

计算方法1:
int lowbit(int x) {
   
	 return x&(x^(x-1));
}

首先设x=6,即其二进制为110 。于是我们使 x-1=101 可以发现,当我们将一个二进制数减一时,从最低位一(即lowbit)开始向后的部分与之前全部相反,因为减去的1对后面的每一位都有影响,同时因为是二进制,影响就是让每一位都取反了。
110就变成101
从最低位一(第二位)开始向后全部相反了 所以我们再与 x 异或一下,那么从lowbit开始往后全是1
110^101=011
然后我们再用x与新数按位与一下 因为 x lowbit 以前的部分是1或0,lowbit 是1,之后的部分都是0,新数 lowbit 之前的部分都是0,lowbit 是1,之后的部分都是1 所以与完之后他们的交集就是 lowbit。
110&011=010
如此,我们就求出了lowbit。

计算方法2:
int lowbit(int x) {
   
	 return x & -x;
}

原理与上面的方法不尽相同 这个式子运用了计算机的补码计算原理 补码计算简单来讲就是原码的反码反加一 如:
0110(2)=6
变为反码后为 0001
再加一为 0010 即它的补码
可以发现变为反码后 x 与反码数字位每一位都不同, 所以当反码加1后神奇的事情发生了,反码会逢1一直进位直到遇到0,且这个0变成了1,所以这个数最后面出现了一个 100… 串。 由于是反码,进位之后由于1的作用使进位的部分全部取反及与原码相同,所以可以发现 lowbit 以前的部分 x 与其补码即 -x 相反, lowbit x 与 -x 都是1,lowbit 以后 x 与 -x 都是0 所以 x&-x 后除了 lowbit 位是1,其余位都是0。

但lowbit与树状数组有神马关系呢?下面将会为你讲解。

树状数组

树状数组可以解决大部分基于区间上的更新以及求和问题。
树状数组可以解决的问题都可以用线段树解决,但这两者有区别:树状数组的系数要少很多,我们可以这样想:高精算法可以解决高精问题,也可以解决A+B这种简单的问题,但我敢保证没人会用高精算法来做A+B问题(除非他想装逼)。

树状数组像前缀和,但也有不同之处。请观下图:

有一些C[i]只有一部分的数组的和。
而且很有规律。
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[i]是x个相邻元素的和,其中x=lowbit(x)。
豁然开朗了。

树状数组的操作:

1.修改值(update):

我们要把第k个元素增加x
我们就每一次相加了后,寻找这个元素的父亲,也就是加lowbit(k)

void update(int k,int x) {
   
	for(;k<=n;k+=lowbit(k))
		BIT[k]+=x;
	return ;
}
2.求L~R的值(update):

这里的ask函数是求1~x的值。
那么L~R的值就是ask( r )-ask( l-1 )
这个ask函数感觉就和update函数互逆。

int ask(int x) {
   
	int ans=0;
	for(int i=x;i;i-=lowbit(i))
		ans+=BIT[i];
	return ans;
}

例题:

1.单点修改,区间查询:
你有一个长度为n的数组,你要进行q次操作。
操作有两类:
1. 1 i x:给定i,x,将a[i]加上x。
2. 2 l r:求l~r的和。
样例输入:							样例输出:
3 2									6
1 2 3
1 2 0
2 1 3
n,q小于等于1000000

这道题就是我们的引入问题:

#include <bits/stdc++.h>
using namespace std;
int n,q,a[1000005];
long long BIT[1000005];

long long lowbit(int x) {
   
	return x & -x;
}

void update(int k,int x) {
   
	for(int i=k;i<=n;i+=lowbit(i))
		BIT[i]+=x;
	return ;
}

long long ask(int x) {
   
	long long ans=0;
	for(int i=x;i;i-=lowbit(i))
		ans+=BIT[i];
	return ans;
}

int main() {
   
	scanf("%d %d",&n,&q);
	for(int i=1;i<=n;i++) {
   
		scanf("%d",&a[i]);
		update(i,a[i]);
	}
	for(int i=1;i<=q;i++) {
   
		int t,l,r;
		scanf("%d %d %d",&t,&l,&r);
		if(t==
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值