线段树
实际上还是称为区间树更好理解一些。
线段:树上的每个节点对应于一个线段(还是叫“区间”更容易理解,区间的起点和终点通常为整数)
不会重叠。同一层节点所代表的区间,加起来是个连续的区间。
叶子节点的区间是单位长度,不能再分了。
线段树是一棵二叉树,树中的每一个结点表示了一个区间[a,b]。a,b通常是整数。每一个叶子节点表示了一个单位区(长度为1)。对于每一个非叶结点所表示的结点[a,b],其左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间[(a+b)/2+1,b](除法去尾取整)。
[1,9]
[1,5] [6,9]
[1,3] [4,5] [6,7] [8,9]
[1,2] 3 4 5 6 7 8 9
1 2
深度为log2(b-a+1) +1
若一个节点对应的区间是[a,b],则其子节点对应的区间分别是[a,(a+b)/2]和[ (a+b)/2+1,b] (除法去尾取整)
线段树的构建
function 以节点v为根建树、v对应区间为[l,r]
{
对节点v初始化
if (l!=r)
{
以v的左孩子为根建树、区间为[l,(l+r)/2]
以v的右孩子为根建树、区间为[(l+r)/2+1,r]
}
}
建树的时间复杂度是O(n) n为根节点对应的区间长度
线段树应用举例
给你一个数的序列A1A2……An。 并且可能多次进行下列两个操作:
1、对序列里面的某个数进行加减
2、询问这个序列里面任意一个连续的子序列AiAi+1……Aj的和是多少。
希望第2个操作每次能在log(n)时间内完成
poj 3264,3468,2528,1151,3321
树状数组
对于序列a,我们设一个数组C
C[i] = a[i – 2k+ 1] + … + a[i]
k为i在二进制下末尾0的个数
2k就是i 保留最右边的1,其余位全变0
i从1开始算
C即为a的树状数组
用lowbit(x)表示x对应的2k,
lowbit(x) = x&(-x)
lowbit(x) 实际上就是x的二进制表示形式
留下最右边的1,其他位都变成0
C[i] = a[i-lowbit(i)+1] + …+ a[i]
C包含哪些项看上去没有规律
树状数组的好处在于能快速求任意区间的和
a[i] + a[i+1] + … + a[j]
设sum(k) = a[1]+a[2]+…+a[k]
则a[i] + a[i+1] + … + a[j] = sum(j)-sum(i-1)
sum(k) = C[n1]+C[n2] + …+ C[nm]
其中 nm= k
ni-1 = ni- lowbit(ni)
有了树状数组,sum(k)就能在O(logN)时间内求出,N是a数组元素个数。而且更新一个a的元素所花的时间也是O(logN)的(因为a更新了C也得更新)。
当数组中的元素有变更时,树状数组就发挥它的优势了,算法如下(修改为给某个节点 i 加上 x ):
(1)当 i<=n 时,执行下一步;否则的话,算法结束;
(2)ci=ci+x ,i=i+lowbit(i)(在 i 的二进制表示的最后加零),返回第一步。
代码实现:
void
change(
int
i,
int
x)
{
while
(i<=n)
{
c[i]=c[i]+x;
i=i+lowbit(i);
}
}
|
poj3321,1195,2155,2182,2352,1177,3667,3067