Теоретический материал

Ответ на запрос о частичной сумме элементов массива A -- rsq(i, j)

Заметим, что rsq(i, j) = rsq(0, j) - rsq(0, i - 1) (для i = 0 положим rsq(0, - 1) = 0). Таким образом, для ответа на запрос rsq(i, j) достаточно научиться отвечать на запрос вида rsq(0, k), который для краткости обозначим rsq(k).

Ответ на запрос rsq(k) вычисляется следующим образом:

rsq(k) = Bk + Bf(k) - 1 + Bf(f(k) - 1) - 1 +...+ Bf(f(...f(k) - 1...) - 1) - 1

Перепишем эту формулу в виде

rsq(k) = Bk0 + Bk1 + Bk2 +...+ Bkn

-- суммирование начинается с Bk, а индекс каждого следующего слагаемого получается из индекса предыдущего по формуле: ki + 1 = f (ki) - 1 = (ki& (ki + 1)) - 1, где k0 = k. Суммирование заканчивается, когда ki становится меньше 0, то есть km + 1 = f (km) - 1 < 0. Такой момент наступает, поскольку ki + 1 = f (ki) - 1$ \le$ki - 1 < ki, то есть каждый следующий индекс строго меньше предыдущего.

Покажем, почему указанная сумма ([*]) действительно будет являться ответом на запрос rsq(k), то есть Bk0 + Bk1 + Bk2 +...+ Bkn = $\displaystyle \sum_{i=0}^{k}$Ai.

Действительно,

Bk0 + Bk1 + Bk2 +...+ Bkn =
= $\displaystyle \sum_{i=f(k_0)}^{k_0}$Ai + $\displaystyle \sum_{i=f(k_1)}^{k_1}$Ai + $\displaystyle \sum_{i=f(k_2)}^{k_2}$Ai +...+ $\displaystyle \sum_{i=f(k_m)}^{k_m}$Ai =
= $\displaystyle \sum_{i=f(k_0)}^{k_0}$Ai + $\displaystyle \sum_{i=f(k_1)}^{f(k_0)-1}$Ai + $\displaystyle \sum_{i=f(k_2)}^{f(k_1)-1}$Ai +...+ $\displaystyle \sum_{i=f(k_m)}^{f(k_{m-1})-1}$Ai =
= $\displaystyle \sum_{i=f(k_m)}^{k_0}$Ai.

Для последнего индекса km верно: f (km) - 1 < 0, откуда в силу неотрицательности f (km) следует f (km) = 0, то есть $\displaystyle \sum_{i=f(k_m)}^{k_0}$Ai = $\displaystyle \sum_{i=0}^{k}$Ai. Таким образом, указанная сумма ([*]) и будет суммой всех элементов массива A с 0-го по k-й.

Например, для k = 14 по формуле ([*]) имеем:

rsq(14) = B14 + B13 + B11 + B7 =
= $\displaystyle \sum_{i=14}^{14}$Ai + $\displaystyle \sum_{i=12}^{13}$Ai + $\displaystyle \sum_{i=8}^{11}$Ai + $\displaystyle \sum_{i=0}^{7}$Ai = $\displaystyle \sum_{i=0}^{14}$Ai.

Ниже приводится текст подпрограммы, реализующий ответ на запрос rsq(k):

 1: Function rsq(k : LongInt) : LongInt;
2: var
3: res : LongInt;
4: begin
5: res := 0;
6: while (k >= 0) do begin
7: inc(res, B[k]);
8: k := k and (k + 1) - 1;
9: end;
10: rsq := res;
11: end;

k0 14 11102
f (k0) 14 11102
k1 13 11012
f (k1) 12 11002
k2 11 10012
f (k2) 8 10002
k3 7 01112
f (k3) 0 0002

 

 

 

 

 

 

 

 

 

Рис. 3.

Для оценки времени работы данной подпрограммы необходимо оценить количество итераций цикла while в строках 6-9, то есть количество слагаемых m в сумме ([*]). Для этого рассмотрим двоичное представление числа k и то, как оно преобразуется при вычислении номера следующего индекса суммы по предыдущему: ki + 1 = f (ki) - 1, где k0 = k.

Сделаем это на примере для k = 14 (рис. 3). Количество слагаемых равно количеству единичных битов в числе f (k) плюс один. Переход к каждому следующему индексу «выбивает» ровно по одному единичному биту из числа f (ki). А поскольку в худшем случае количество единичных бит в f (k) может быть порядка O(log N), то и ответ на запрос rsq(k) в худшем случае требует порядка O(log N) времени.