学习总结之树状数组

树状数组和线段树听说是区分ACMer和其他人的重要区别,嘿嘿嘿。。。
树状数组总体而言可以总结为一个很简单的问题复杂化,但是时间简单化。
问题的引入:
给定n个数,a[1]…a[n]。每次我们可能有两种操作:
(1)求出a[i]…a[j]的和;
(2)给a[x]的值加上一个值val。
n的规模如果比较大(约100000)
该如何高效的实现?
这个时候如果我们通过暴力求解的话,1000ms的时间内如果数据达到了10e5就会直接超时,总体而言我们要找到一种新的方法进行工作,树状数组应运而生。
在这里插入图片描述
这个图片太精彩了!!
方格中数字代表对应数组的第几个元素,下排是a数组,其上方的是e数组,最下的二进制则是对应编号的二进制表示.
箭头表示这个数组元素被哪个数组元素包含了,比如e[2]=e[1]+a[2]=a[1]+a[2], e[4]=e[2]+e[3]+a[4]=e[2]+a[3]+a[4]=a[1]+a[2]+a[3]+a[4].
注意观察:
1.a的每个元素至多仅被E的一个元素包含,这点和树有很大相同,但整体并不是树
2.每个ei可认为是仅包含ai和其它若干个e元素
3.每个ei包含的元素数目(不包括ai在内)为i的二进制表示中末位连续的0的个数

同时我们也会对于这个树状数组的应用产生以下的疑问:
1.对应二进制数的最末连续0的个数如何得知?
2.如何存储?
3.如何在修改时访问需要访问的元素?
4.如何在求和时访问需要访问的元素?
就让我们一个一个的来解决!
1、通过位运算进行解决
在实际应用中,我们需要的不是最末连续的0的个数,而是最末那段”1000”对应的十进制数
lowbit(x) := (((x-1) xor x) and x);
或者:
lowbit(x) := ((-x) and x);
2、存储直接使用一维数组就可以啦
如何访问父亲和兄弟?
由于某元素和其父亲,最近的兄弟在下标上存在关系(与lowbit(x)有关),利用这个关系就可以用一维数组存储了,而且稍后你会发现,
存储e数组即可,无需存储a数组.
3、事实上修改操作只涉及到父节点的访问
经过观察和探究,前人们得出了这个规律:
父亲:比他大的,离他最近的,末位连续0比他多的数就是他的父亲,X节点父亲的编号=x+lowbit(x)
4、当我们求1…x的信息时,e[x]如果包含的不是1…x的全部信息,(比如e[6]=a[5]+a[6])就需要再找一个ek累加起来,这个k我们称之为x的前驱,举个例子:
a[1]+a[2]+…+a[6]=e[6]+e[4],a[1]+a[2]+…a[7]=e[7]+e[6]+e[4]
前驱的编号即为比自己小的,最近的,最末连续0比自己多的数
所以前驱=x-lowbit(x)

当然这些都可以用我做的下面的代码进行实现,为了方便理解,我通过循环把图片直接转换为了代码形式

#include<iostream>
using namespace std;
int lowbit(int x)
{
	return (-x) & x;
}
int change(int x)
{
	int a = 0;
	while (x != 0)
	{
		a = a * 10 + x % 2;
		x /= 2;
	}
	return a;
}
int getfather(int x)
{
	return x + lowbit(x);
}
int getson(int x)
{
	return x - lowbit(x);
}
int main()
{
	int i;
	for (i = 1; i <= 10; ++i)
	{
		cout << i << "  二进制:" << change(i) << "  二进制0的个数:" << lowbit(i) << " 他的父亲节点:" << getfather(i) << "他的前驱:" << getson(i) << endl;
	}
	return 0;
}

不方便编译可以直接看我的答案:

1  二进制:1  二进制0的个数:1 他的父亲节点:2他的前驱:0
2  二进制:1  二进制0的个数:2 他的父亲节点:4他的前驱:0
3  二进制:11  二进制0的个数:1 他的父亲节点:4他的前驱:2
4  二进制:1  二进制0的个数:4 他的父亲节点:8他的前驱:0
5  二进制:101  二进制0的个数:1 他的父亲节点:6他的前驱:4
6  二进制:11  二进制0的个数:2 他的父亲节点:8他的前驱:4
7  二进制:111  二进制0的个数:1 他的父亲节点:8他的前驱:6
8  二进制:1  二进制0的个数:8 他的父亲节点:16他的前驱:0
9  二进制:1001  二进制0的个数:1 他的父亲节点:10他的前驱:8
10  二进制:101  二进制0的个数:2 他的父亲节点:12他的前驱:8

是不是很精彩呢~~

发布了90 篇原创文章 · 获赞 15 · 访问量 3152

猜你喜欢

转载自blog.csdn.net/qq_43656233/article/details/90695140