树状数组解析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/MMMMMMMW/article/details/81182945

先来一张图

我觉得把这张图理解了,树状数组也就差不多理解了。

树状数组采用的是二进制的形式,功能跟线状树差不多,但比线状树要简洁很多。

下面来一一讲解下上图内容。

对于数组a[1],a[2]...a[n],有他们相应的树状数组sum[1],sum[2]...sum[n]来表示它们的和

a[1],1的二进制是0001,最后的1在倒数第1,其树状数组sum[1]表示a[1]          

a[2],2的二进制是0010,最后的1在倒数第2,其树状数组sum[2]表示a[1]+a[2]  

a[3],3的二进制是0011,最后的1在倒数第1,其树状数组sum[3]表示a[3]                  

a[4],4的二进制是0100,最后的1在倒数第3,其树状数组sum[4]表示a[1]+a[2]+a[3]+a[4]

a[5],5的二进制是0101,最后的1在倒数第1,其树状数组sum[5]表示a[5]          

a[6],6的二进制是0110,最后的1在倒数第2,其树状数组sum[6]表示a[5]+a[6]  

a[7],7的二进制是0111,最后的1在倒数第1,其树状数组sum[7]表示a[7]                  

a[8],8的二进制是1000,最后的1在倒数第4,其树状数组sum[8]表示a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8]

比如我们要求a[1]到a[7]的和,就需要先求sum[7],即a[7],之后7的二进制0111减去最后一个1,得0110,即6,加上sum[6],得a[5]+a[6],0110再减去最后一个1,得0100,即sum[4],最后加上a[1],a[2],a[3],a[4],得到答案。

解释一下查找的原理

7的二进制是0111,即2^0+2^1+2^2,只需要遍历三次,第一次加2^0 = 1个数组的值,即a[7],第二次加2^1 = 2个数组的值,即a[5]和a[6],第三次加2^3 = 4个数组的值,即a[1],a[2],a[3],a[4]。

同理,8的二进制是1000,即2^3,只需要遍历一次,第一次就加了2^3 = 8个数组的值。

总而言之,树状数组关键就在于利用二进制的最后一个1,查找就是向下加,修改就是向上加。

下面是模板:

int lowbit(int x)//查找x二进制的最后一个1
{
	return x&-x;
	//或者x-(x&(x-1))
}
void update(int p,int x)//在p位置加上x,之后向上遍历前缀和
{
	for(int i = p;i <= n;i+=lowbit(i))
		sum[i]+=x;
}
int Find(int p)//查找1--p的和,向下遍历
{
	int ans = 0;
	for(int i = p;i > 0;i-=lowbit(i))
		ans+=sum[i];
	return ans;
}

顺便讲一下树状数组的逆序数应用,也就是求一个数组中有多少对逆序对

逆序数

简而言之就是位置与数值相对的一对数,比如2和1,其中2在1前面,但2比1大,那2和1就是一堆逆序数

思路

正常数据范围内,我们可以按顺序查找一个数组,每次查找后树状数组中对应的该值加1,数组下表就是该数值,并向上遍历更新,此后,我们再查找该值在树状数组的前缀和,显然,只要出现过一个比该值小的数,前缀和就会加1,这样我们就可以算出前i个数中比第i个数小的数量,i减去找到的数量就是逆序数,循环一遍即可。

变态数据范围内,比如ai的值很大很大,这样再开树状数组就会爆掉,于是我们可以用离散化来解。

建立一个结构体,里面有数组值和数组位置,然后数组值按从小到大排序,循环一遍数组,每次循环都先将树状数组中,下标对应该值位置的数组加1,然后向上遍历更新前缀和。最后查找数组数组中的该位置,其结果就是位置和值都在第i个数前面的数,i减去该值就是逆序数的数量

猜你喜欢

转载自blog.csdn.net/MMMMMMMW/article/details/81182945