TreeArray

树状数组是对一个数组改变某个元素和求和比较实用的数据结构。

设a[1...N]为原数组,定义c[1...N]为对应的树状数组:

c[i] = a[i - 2^k + 1] + a[i - 2^k + 2] + ... + a[i]

其中k为i的二进制表示末尾0的个数,所以2^k即为i的二进制表示的最后一个1的权值.

也就是说,把k表示成二进制1***10000,那么c[k]就是1***00001 + 1***00010 + ... + 1***10000这一段数的和。

所以2^k可以表示为n&(n^(n-1))或者更简单的n&(-n),例如:

为了表示简便,假设现在一个int型为4位,最高位为符号位

int i=3&(-3);     此时i=1,3的二进制为0011,-3的二进制为1101(负数存的是补码)所以0011&1101=1

int Lowbit(int t)

{

return t & ( t ^ ( t - 1 ) );

}

求得:2^k=1000,

也就是说,把k表示成二进制1***10000,那么c[k]就是1***00001 + 1***00010 + ... + 1***10000这一段数的和。

设节点编号为x,由于c[i] = a[i - 2^k + 1] + a[i - 2^k + 2] + ... + a[i]

那么这个节点管辖的区间为2^k个元素

修改一个节点,必须修改其所有祖先,最坏情况下为修改第一个元素,最多有log(n)的祖先。

如上图,对于c[8],c[4],假设c[4]的右兄弟节点为c[x],那么c[4]和c[x]所管辖的节点数目是一样的,都是4个,即展开以后得到A数组中元素数目都是4,求c[4]节点父节点的编号,即是求父节点最大能扩展到多少,对于c[8]来说,可以认为他从左孩子的末端开始(即左孩子最大扩展的编号)计算,延伸的宽度即是左孩子的宽度,即为lowb(4)

修改一个节点,必须修改其所有祖先,最坏情况下为修改第一个元素,最多有log(n)的祖先。

对a[n]进行修改后,需要相应的修改c数组中的p1, p2, p3...等一系列元素

其中p1 = n,  pi+1 = pi + lowbit(pi),

void Modify(int n, int delta)

{

while(n <= N)

{

c[n] += delta;

n += lowbit(n);

}

}

当要查询a[1],a[2]...a[n]的元素之和时,需要累加c数组中的q1, q2, q3...等一系列元素

其中q1  = n,qi+1 = qi - lowbit(qi)

所以计算a[1] + a[2] + .. a[n]可以实现为:

int Sum(int n)

{

int result = 0;

while(n != 0)

{

result += c[n];

n -= lowbit(n);

}

return result;

}

为什么是效率是log(n)的呢?以下给出证明:

n = n – lowbit(n)这一步实际上等价于将n的二进制的最后一个1减去。而n的二进制里最多有log(n)个1,所以查询效率是log(n)的。

时间: 2024-10-13 00:34:55

TreeArray的相关文章

设计模式之蝇量模式

蝇量模式:让某个类的一个实例能够用来提供多个"虚拟"实例,运用共享技术有效地支持大量细粒度的对象 特点: 减少运行时对象实例的个数 将许多"虚拟"对象的状态一同管理 运用共享技术有效地支持大量细粒度的对象 区分对象的共享变量(内部状态)和不可共享变量(外部状态,将此类变量从类从剔除,由外部传入) 用途: 当一个类需要创建很多个实例,而这些实例可以被同一个方法控制 缺点: 单个逻辑实例将无法拥有独立不同的行为 举例: 你需要建立很多树对象,每个树对象有三个属性:位置坐

SYSU-5, POJ 2131, 树状数组+二分

题目大意:给出n个人,顺序对位置进行请求,如果第i个人请求的位置上有人,则让这个人顺延,如果顺延的位置继续有人,递归进行,问最后所有人的位置. 解:这题貌似可以用平衡树+并查集搞定,但是我队友强烈安利树状数组的做法.赛场上没出,赛后结合discuz想了一下,作一下处理. 首先如果是一个请求第a[i]个有空位置的问题,那么这个问题显然可以用树状数组维护前缀和即可.所以我们现在考虑将原问题转化成这个问题. 考虑终态,把没有人的位置去掉,剩下的n个座位排在一起,显然转化成上面模型的形式 第i个询问时,

树状数组求逆序对:POJ 2299、3067

前几天开始看树状数组了,然后开始找题来刷. 首先是 POJ 2299 Ultra-QuickSort: http://poj.org/problem?id=2299 这题是指给你一个无序序列,只能交换相邻的两数使它有序,要你求出交换的次数.实质上就是求逆序对,网上有很多人说它的原理是冒泡排序,可以用归并排序来求出,但我一时间想不出它是如何和归并排序搭上边的(当初排序没学好啊~),只好用刚学过的树状数组来解决了.在POJ 1990中学到了如何在实际中应用上树状数组,没错,就是用个特殊的数组来记录即

POJ 1990 MooFest 树状数组

这题是我看了大白书树状数组后刷的第一道题,确实难度不小,所以只好上网找题解了,网上的做法确实精彩.这题的题意主要是有N头牛,每两头牛之间交流的费用为它们的距离乘上两者音量的最大值(即max(v(i),v(j))),然后统计所有牛两两交流的总费用.一开始能想到的做法便是O(n2)的暴力枚举了,当时的我也只能想到这样的复杂度,很纳闷怎么能和树状数组搭上边呢?然后看了别人的题解后才惊叹其思路之妙. 在博客 http://www.cnblogs.com/Fatedayt/archive/2011/10/

UESTC 2016 Summer Training #5 Div.2(未完待续)

A #include <cstdio> #include <cstring> #include <vector> #define MAXN 100005 #define mem(a) memset(a, 0, sizeof(a)) using namespace std; int TreeArray[MAXN], Left[MAXN], Right[MAXN], Fork[MAXN]; typedef vector<int> Ve; vector<Ve

基础数据结构-二叉树-拓展:基于数组存储的构建

用数组存储与前文是类似的,只是换了一个储存方式,有兴趣可以看一下下面的代码,具体就不解释了. #include<iostream> #include<string> using namespace std; class BiTreeNode { public: char data; //结点数据 BiTreeNode *LeftChild; //左子树指针 BiTreeNode *RightChild; //右子树指针 BiTreeNode():LeftChild(NULL),Ri

蝇量模式——HeadFirst设计模式学习笔记

蝇量模式:让某个类的一个实例能够用来提供多个"虚拟"实例,运用共享技术有效地支持大量细粒度的对象 特点: 减少运行时对象实例的个数 将许多"虚拟"对象的状态一同管理 运用共享技术有效地支持大量细粒度的对象 区分对象的共享变量(内部状态)和不可共享变量(外部状态,将此类变量从类从剔除,由外部传入) 用途: 当一个类需要创建很多个实例,而这些实例可以被同一个方法控制 缺点: 单个逻辑实例将无法拥有独立不同的行为 举例: 你需要建立很多树对象,每个树对象有三个属性:位置坐

CF 540E, 树状数组

题目大意:在1~10^9的范围内随便交换某些位置上的数,求逆序对数量,交换位置<=10^5 解:因为是交换位置很少,离散化来做,逆序对可以看成两部分,一部分是出现位置的逆序对,另一部分的出现了的数对于没有交换位置上的数(没有在离散化中出现的数)的逆序对.分别统计一下,第一part用树状数组,第二part之间算一下区间实际的数字和有多少个交换了位置的数字即可. 1 #include <cstdio> 2 #include <iostream> 3 #include <al

树状数组模板 Tree Array

---恢复内容开始--- 这几天去浙江省选当垫底(Orz我是蒟蒻),然后顺便复习下树状数组 关于树状数组,其实很好理解,主要就是lowbit()操作的巧妙 树状数组是一种非常优雅的数据结构.当要频繁的对数组元素进行修改,同时又要频繁的查询数组内任一区间元素之和的时候,可以考虑使用树状数组. 换句话说,树状数组最基本的应用: 对于一个数组,如果有多次操作,每次的操作有两种:1.修改数组中某一元素的值,2.求和,求数组元素a[1]+a[2]+…a[num]的和. ——Km的小天地 其实树状数组自己写