Теоретический материал
Ответ на запрос о частичной сумме элементов массива 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), то есть Bk0 + Bk1 + Bk2 +...+ Bkn = Ai.
Действительно,
=




=




=

Для последнего индекса km верно: f (km) - 1 < 0, откуда в силу неотрицательности f (km) следует f (km) = 0, то есть Ai =
Ai. Таким образом, указанная сумма ([*]) и будет суммой всех элементов массива A с 0-го по k-й.
Например, для k = 14 по формуле ([*]) имеем:
=





Ниже приводится текст подпрограммы, реализующий ответ на запрос 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) времени.