树状数组整理

来源:互联网 发布:国二c语言题库编程题 编辑:程序博客网 时间:2024/06/06 04:23

树状数组经典模型

1、PUIQ模型

【例题1】一个长度为n(n <= 500000)的元素序列,一开始都为0,现给出三种操作:

1. add x v :    给第x个元素的值加上v;     (  a[x] += v )

2. sub x v :    给第x个元素的值减去v;     (  a[x] -= v )

3. sum x y:  询问第x到第y个元素的和;  ( print sum{ a[i] | x <= i <= y } )

这是树状数组最基础的模型,1和2的操作就是对应的单点更新,3的操作就对应了成端求和

具体得,1和2只要分别调用add(x, v)add(x, -v), 而3则是输出sum(y) – sum(x-1)的值。

我把这类问题叫做PUIQ模型(Point Update Interval Query点更新,段求和)。

推广至二维情况甚至三维情况:

1. add x y v :    给(x,y)个元素的值加上v;     (  a[x][y] += v )

2. sub x y v :    给(x,y)个元素的值减去v;     (  a[x][y] -= v )

3. sum x1 y1 x2 y2:  询问第(x1,y1)个元素到第(x2,y2)元素的和;  ( print sum{ a[i][j] | x1 <= i <= x2,y1 <= j <= y2 } )

1和2只要分别调用add(x,y, v)add(x,y, -v), 而3则是输出sum(x2,y2) + sum(x1-1,y1-1) - sum(x2,y1-1) - sum(x1-1,y2)【四个四边形面积关系】的值。

三维同理。

模板题HDU 1166 敌兵布阵,POJ 2155 MatrixPOJ 1195 Mobile phones

2、IUPQ模型

【例题2】一个长度为n(n <= 500000)的元素序列,一开始都为0,现给出两种操作:
1. add x y v :    给第x个元素到第y个元素的值都加上v;     (  a[i] += v, 其中 x <= i <= y )
2. get x:         询问第x个元素的值;                               (  print  a[x] )

这类问题对树状数组稍微进行了一个转化,但是还是可以用add和sum这两个函数来解决,对于操作1我们只需要执行两个操作,即add(x, v)add(y+1, -v);而操作2则是输出sum(x)的值。

为什么这样就可以呢?考虑 sum(i) 为位置 i 的前缀和,若我们将其当成 ai 位置的值,那之前的更新就是成功的。比如说一开始要使 [1,5] 的值增加2,按我们的做法,便只有两步操作 add(1,2) 和 add(5,−2)。这样当你查询 a3 的值时,sum(3) 就会计算到之前位置 1 增加的 2。就相当于 3 这个位置增加了 2。

这样就把区间更新转化成了单点更新,单点求值转化成了区间求和。

我把这类问题叫做IUPQ模型(Interval Update Point Query段更新点求值)。

推广至二维情况:

1. add x1 y1 x2 y2 v :    给第x个元素到第y个元素的值都加上v;     (  a[i][j] += v, 其中 x1 <= i <= x2,y1 <= j <= y2 )
2. get x y:         询问第(x,y)个元素的值;                               (  print  a[x][y] )

对于操作1我们只需要执行两个操作,即add(x1, y1, v)add(x1,y2+1, -v)add(x2+1,y1,-v)add(x2+1,y2+1,v)

而操作2则是输出sum(x,y)的值。


两道题目HDOJ 1556 Color the ballHDU 3584 Cube(三维树状数组)


3、逆序模型

【例题3】给定一个长度为n(n <= 500000)的排列a[i],求它的逆序对对数。1 5 2 4 3 的逆序对为(5,2)(5,3)(5,4)(4,3),所以答案为4。

那么我们只需要对这个排列从后往前枚举,每次枚举到 Xi 元素时,执行cnt += sum(Xi-1),然后再执行add(Xi, 1),n个元素枚举完毕,得到的cnt值就是我们要求的逆序数了。总的时间复杂度O(nlogn)。

这个模型和之前的区别在于它不是将原数组的下标作为树状数组的下标,而是将元素本身作为树状数组的下标。逆序模型作为树状数组的一个经典思想有着非常广泛的应用。

朴素算法,枚举任意两个数,判断他们的大小关系进行统计,时间复杂度O(n2)。不推荐!来看一个给定n个元素的排列 X0 X1 X2 … Xn-2 Xn-1,对于某个 Xi 元素,如果想知道以它为”首”的逆序对的对数( 形如(XiXj) 的逆序对),就是需要知道 Xi+1 … Xn-2 Xn-1 这个子序列中小于Xi 的元素的个数

其实很多问题都可以转化为逆序问题。

题目: POJ 2299 Ultra-QuickSort,HDU 1394 Minimum Inversion Number

POJ 3067 Japan也可以看成求逆序对的题目。

不光可以求逆序对,还可以求顺序对。

4、二分模型

【例题4】给定N(N <= 100000)个编号为1-N的球,将它们乱序丢入一个“神奇的容器”中,作者会在丢的同时询问其中编号第K大的那个球,“神奇的容器”都能够从容作答,并且将那个球给吐出来,然后下次又可以继续往里丟。

现在要你来模拟这个神奇的容器的功能。可以抽象成两种操作:

1. put x                 向容器中放入一个编号为x的球;
2. query K             询问容器中第K大的那个球,并且将那个球从容器中去除(保证K<容器中球的总数);

这个问题其实就是一维Median Filter的原型了,只是那个问题的K = r+1,而这里的K是用户输入的一个常量。所谓二分模型就是在求和的过程中,利用求和函数的单调性进行二分枚举。

对于操作1,只是单纯地执行add(x, 1)即可;而对于操作2,我们要看第K大的数满足什么性质,由于这里的数字不会有重复,所以一个显而易见的性质就是一定有K-1个数大于它,假设它的值为x,那么必然满足下面的等式:sum(N) – sum( x ) == K-1,然而,满足这个等式的x可能并不止一个,来看下面的图:


图中灰色的格子表示容器中的球,分别为2、3、7、8,然后我们需要求第3大的球,理论的球编号为3,但是满足上面等式的球的编号为3、4、5、6。所以我们需要再加一个限制条件,即满足上面等式的最小的x。于是我们二分枚举x,当满足sum(N) – sum( x ) <= K-1时,将右区间缩小(说明找的数x偏大,继续往小的找),否则左区间增大(说明找的数x偏小,继续往大的找),直到找到满足条件的最小的x为止。单次操作的时间复杂度为O( logn * logn )。


参考资料:

1. 夜深人静写算法(3):树状数组 - 文章 - 伯乐在线

0 0