算法基础:打开程序设计之门
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

1.2 树状数组

树状数组(Binary Indexed Tree,BIT)能够高效地求序列区间和。树状数组的实现简单,巧妙地运用了二进制的思想。

1.2.1 树状数组的定义

给定一个数组进行两个操作:一是更新某点的值;二是求某段区间的和。对于普通的数组,单点更新的复杂度为O(1),求区间和的复杂度为On),而树状数组能够把单点更新和区间求和的复杂度都变为Onlogn)。

设数组A[]为原数组,定义C[i]=A[i-2k+1]+…+A[i]。其中,ki用二进制表示时的末尾0的个数,如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[i]就是从A[i]开始前2k项的和,如图1.3所示。

图1.3 树状数组

1.2.2 树状数组的实现和使用

kx用二进制表示时末尾0的个数,对于2k,有一种快速的求解方法:

当修改某一点的值时,只需要修改某一点的所有父节点就可以了。对于一个节点i来说,它的父节点的编号为i+lowbit(i)。因此对于单点修改的函数为

n个数的和记为sum(n),已知C[i]是从i开始向前lowbit(i)个数的和,所以sum(n)=C[n]+sum[n-lowbit(n)]。

区间[start,end]的区间和可以通过sum(end)-sum(start)来求得。树状数组也可以进行区间增减更新和单点查询操作。A[]是原数组,构造差分数组D[],令D[1]=A[1],D[i]=A[i]-A[i-1](i>1),则数组A是数组D的前缀和,即A[i]=D[1]+D[2]+…+D[i]。这样求A的单点值就是求D的区间和。更新A某段区间[start,end]的值只需要更改D[start]和D[end+1]的值就可以了。这两个操作可以通过构造D的树状数组求得。

1.2.3 例题讲解

例1-1 Sort it

Time Limit:2000/1000 ms(Java/Others) Memory Limit:32768/32768 KB(Java/Others)

题目描述:

给定一个由n个不同的整数组成的序列,通过交换相邻的两个数,使得序列变成上升的。问最少需要交换多少次,如“1 2 3 5 4”,需要进行一次操作,交换5和4。

输入:

输入包含多组数据,每一组数据包含两行,第一行为一个正整数nn≤1000),第二行为从1到nn个整数的一个序列。

输出:

输出为1行,输出最少需要交换的次数。

样例输入:

3

1 2 3

4

4 3 2 1

样例输出:

0

6

题目来源:HDOJ 2689。

解题思路:

题目可以转化成求数组中逆序对的数量。

逆序对:设数组A为一个有n个数字的有序集(n>1),其中的数字均不相同,如果存在正整数ij,使得1≤i<jn而且A[i]>A[j],则<A[i],A[j]>这个有序对称为A的一个逆序对。

对于每一个数字,求它前面有多少个数字大于它,即该数字的贡献,所有数字的贡献和即逆序对的数量。对于A[i]=x,在树状数组的x处加1,然后求sum(x)就是在x前面有多少个数小于等于x,那么i-sum(x)就是A[i]的贡献。从左到右对于每一个数边插入边求和。

题目实现: