Главная страница
Навигация по странице:

  • 9.1. Запросы к статическим массивам

  • 9.1.1. Запросы о сумме

  • Рис. 9.2.

  • 9.2. Древовидные структуры

  • 9.2.1. Двоичные индексные деревья

  • Рис. 9.6.

  • Рис. 9.9.

  • 9.2.2. Деревья отрезков

  • Рис. 9.12.

  • Рис. 9.13.

  • Рис. 9.15.

  • анти. Guide to Competitive


    Скачать 2.39 Mb.
    НазваниеGuide to Competitive
    Дата20.12.2022
    Размер2.39 Mb.
    Формат файлаpdf
    Имя файлаанти.pdf
    ТипGuide
    #854732
    страница11 из 26
    1   ...   7   8   9   10   11   12   13   14   ...   26
    Глава
    9
    Запросы по диапазону
    В этой главе мы обсудим структуры данных для эффективной обработки запросов к массивам по диапазону. Типичные примеры таких запросов: вычисление суммы по диапазону и нахождение минимума по диапазону.
    В разделе 9.1 рассматривается простая ситуация, когда массив не мо- дифицируется между запросами. В таком случае достаточно подвергнуть массив предварительной обработке, чтобы можно было эффективно най- ти ответ на любой возможный запрос. Сначала мы научимся обрабатывать запросы о сумме с помощью массива префиксных сумм, а затем обсудим алгоритм разреженной таблицы для обработки запросов о минимуме.
    В разделе 9.2 представлены две древовидные структуры, позволяющие эффективно обрабатывать запросы и изменять значения элементов мас- сива. Двоичное индексное дерево поддерживает запросы о сумме и может рассматриваться как динамический вариант массива префиксных сумм.
    Дерево отрезков – более универсальная структура, поддерживающая за- просы о сумме, запросы о минимуме и некоторые другие. Все операции над обеими структурами имеют логарифмическую сложность.
    9.1. Запросы к статическим массивам
    В этом разделе мы рассмотрим ситуацию, когда массив статический, т. е. не изменяется между запросами. Тогда достаточно подвергнуть его пред- варительной обработке с целью эффективного получения ответов на за- просы по диапазону.
    Сначала обсудим простой способ обработки запросов о сумме с помощью массива префиксных сумм, который допускает обобщение на многомер- ный случай. Затем изучим несколько более сложный алгоритм разрежен- ной таблицы для обработки запросов о минимуме. Запросы о максимуме обрабатываются аналогично.
    9.1.1. Запросы о сумме
    Обозначим sum
    q
    (a, b)(«запрос о сумме по диапазону») сумму элементов массива в диапазоне [a, b]. Любой запрос о сумме можно эффективно об- работать, если сначала построить массив префиксных сумм. Каждый эле-

    9.1. Запросы к статическим массивам

    145
    мент этого массива равен сумме нескольких первых элементов исходного массива, т. е. значение k-го элемента равно sum
    q
    (0, k). На рис. 9.1 показаны массив и соответствующий ему массив префиксных сумм.
    1 3
    4 8
    6 1
    4 2
    0 1
    2 3
    4 5
    6 7
    original array
    1 4
    8 16 22 23 27 29 0
    1 2
    3 4
    5 6
    7
    prefix sum array
    Исходный массив
    Массив префиксных сумм
    Рис. 9.1. Массив и соответствующий ему массив префиксных сумм
    Массив префиксных сумм можно построить за время O(n). Поскольку он содержит значения sum
    q
    (0, k) для всех k, то любое значение вида sum
    q
    (a, b) можно вычислить за время O(1)по формуле sum
    q
    (a, b) = sum
    q
    (0, b) – sum
    q
    (0, a – 1).
    Положив sum
    q
    (0,–1) = 0, мы распространим эту формулу и на случай a = 0.
    На рис. 9.2 показано, как вычисляется сумма по диапазону [3, 6] с по- мощью массива префиксных сумм. Легко вычислить, что sum
    q
    (3, 6) = 8 + 6 +
    1 + 4 = 19. Пользуясь же массивом префиксных сумм, мы можем обойтись всего двумя значениями:
    sum
    q
    (3, 6) = sum
    q
    (0, 6) − sum
    q
    (0, 2) = 27 − 8 = 19.
    1 3
    4 8
    6 1
    4 2
    0 1
    2 3
    4 5
    6 7
    original array
    1 4
    8 16 22 23 27 29 0
    1 2
    3 4
    5 6
    7
    prefix sum array
    Исходный массив
    Массив префиксных сумм
    Рис. 9.2. Вычисление запроса о сумме по диапазону с помощью массива префиксных сумм
    Многомерный случай. Эта идея обобщается и на многомерный случай.
    На рис. 9.3 показан двумерный массив префиксных сумм, который можно использовать для вычисления суммы по любому прямоугольному подмас- сиву за время O(1). Каждая сумма в этом массиве соответствует подмасси- ву, начинающемуся в левом верхнем углу массива. Сумму по серому под- массиву можно вычислить по формуле
    S(A) − S(B) − S(C) + S(D),
    где S(X)– сумма элементов прямоугольного подмассива, занимающего ле- вый верхний угол исходного массива вплоть до позиции X.

    146
    Глава 9. Запросы по диапазону
    A
    B
    C
    D
    Рис. 9.3. Вычисление суммы по диапазону в двумерном случае
    9.1.2. Запросы о минимуме
    Обозначим min
    q
    (a, b)(«запрос о минимуме по диапазону») минималь- ный элемент массива в диапазоне [a, b]. Мы рассмотрим метод, позволя- ющий обработать запрос о минимуме по диапазону за время O(1)после предварительной обработки, занимающей время O(n log n). Этот метод, предложенный в работе Bender, Farach-Colton [3], часто называют алго-
    ритмом разреженной таблицы.
    Идея заключается в том, чтобы заранее вычислить min
    q
    (a, b)для случаев, когда b a + 1 (длина диапазона) является степенью двойки. На рис. 9.4 показаны предвычисленные значения для массива из 8 элементов.
    1 3
    4 8
    6 1
    4 2
    0 1
    2 3
    4 5
    6 7
    original array
    1 3
    4 6
    1 1
    2

    0 1
    2 3
    4 5
    6 7
    range size 2 1
    3 1
    1 1



    0 1
    2 3
    4 5
    6 7
    range size 4 1







    0 1
    2 3
    4 5
    6 7
    range size 8
    Размер диапазона 2
    Размер диапазона 4
    Размер диапазона 8
    Исходный массив
    Рис. 9.4. Предварительная обработка для запросов о минимуме
    Количество предвычисляемых значений имеет порядок O(n log n), по- скольку существует O(log n) длин диапазонов, являющихся степенью двой- ки. Эти значения можно эффективно вычислить по рекуррентной формуле min
    q
    (a, b) = min(
    min
    q
    (a, a + w − 1), min
    q
    (a + w, b)),
    где b a + 1 – степень двойки, а w = (b a + 1)/2. Вычисление всех значений занимает время O(n log n).

    9.2. Древовидные структуры

    147
    После этого любое значение min
    q
    (a, b)можно вычислить за время O(1), взяв минимум двух предвычисленных значений. Пусть k – наибольшая степень двойки, не превосходящая b a + 1. Значение min
    q
    (a, b) вычисля- ется по формуле min
    q
    (a, b) = min(
    min
    q
    (a, a + k − 1), min
    q
    (bk + 1, b)).
    В этой формуле диапазон [a, b] представлен как объединение двух диа- пазонов длины k: [a, a + k − 1] и [b k + 1, b].
    Для примера рассмотрим диапазон [1,6] на рис. 9.5. Его длина равна 6, а максимальная степень двойки, не превосходящая 6, – это 4. Таким об- разом, диапазон [1,6] является объединением диапазонов [1,4] и [3,6].
    Поскольку min
    q
    (1, 4)= 3 и min
    q
    (3,6)= 1, можно заключить, что min
    q
    (1, 6) = 1.
    1 3
    4 8
    6 1
    4 2
    0 1
    2 3
    4 5
    6 7
    range size 6 1
    3 4
    8 6
    1 4
    2 0
    1 2
    3 4
    5 6
    7
    range size 4 1
    3 4
    8 6
    1 4
    2 0
    1 2
    3 4
    5 6
    7
    range size 4
    Размер диапазона 6
    Размер диапазона 4
    Размер диапазона 4
    Рис. 9.5. Вычисление минимума по диапазону с помощью двух перекрывающихся диапазонов
    Отметим, что с помощью довольно сложных методов можно обработать запрос о минимуме по диапазону за время O(1)после предобработки, за- нимающей лишь время O(n)(см., например, Fischer, Heun [12]), но эти ме- тоды выходят за рамки книги.
    9.2. Древовидные структуры
    В этом разделе мы представим две древовидные структуры, позволяющие обрабатывать запросы по диапазону и изменять значения элементов мас- сива за логарифмическое время. Сначала обсудим двоичные индексные деревья, поддерживающие запросы о сумме, а затем – деревья отрезков, поддерживающие также запросы других видов.
    9.2.1. Двоичные индексные деревья
    Двоичное индексное дерево (или дерево Фенвика) [11] можно рассматри- вать как динамический вариант массива префиксных сумм. Оно предо- ставляет две операции с временной сложностью O(log n): обработка за- проса о сумме по диапазону и изменение значения. Хотя в названии этой

    148
    Глава 9. Запросы по диапазону структуры данных фигурирует слово «дерево», обычно она представляется в виде массива. При обсуждении двоичных индексных деревьев мы будем предполагать, что все массивы индексируются, начиная с 1, потому что это упрощает реализацию структуры.
    Обозначим p(k)наибольшую степень двойки, делящую k. Двоичное ин- дексное дерево хранится в массиве tree таким образом, что tree
    [
    k] = sum
    q
    (kp(k) + 1, k),
    т. е. элемент в позиции k содержит сумму по заканчивающемуся в этой позиции диапазону длины p(k). Например, поскольку p(6) = 2, tree
    [6] со- держит значение sum
    q
    (5, 6). На рис. 9.6 показаны массив и соответствующее ему двоичное индексное дерево. На рис. 9.7 показано соответствие между значениями двоичного индексного дерева и диапазонами исходного мас- сива.
    1 3
    4 8
    6 1
    4 2
    1 2
    3 4
    5 6
    7 8
    original array
    1 4
    4 16 6
    7 4
    29 1
    2 3
    4 5
    6 7
    8
    binary indexed tree
    Исходный массив
    Двоичное индекcное дерево
    Рис. 9.6. Массив и его двоичное индекcное дерево
    1 4
    4 16 6
    7 4
    29 1
    2 3
    4 5
    6 7
    8
    Рис. 9.7. Соответствие между двоичным индексным деревом и диапазонами
    Зная двоичное индексное дерево, мы можем вычислить любое значение вида sum
    q
    (1, k)за время O(log n), поскольку диапазон [1, k] всегда можно разбить на O(log n)поддиапазонов, суммы по которым хранятся в дере- ве. Например, чтобы вычислить sum
    q
    (1, 7), мы разобьем диапазон [1, 7] на три поддиапазона: [1, 4], [5, 6] и [7, 7] (рис. 9.8). Поскольку суммы по этим поддиапазонам уже имеются в дереве, то мы можем вычислить сумму по всему диапазону по формуле:
    sum
    q
    (1, 7) = sum
    q
    (1, 4) + sum
    q
    (5, 6) + sum
    q
    (7, 7) = 16 + 7 + 4 = 27.

    9.2. Древовидные структуры

    149
    1 4
    4 16 6
    7 4
    29 1
    2 3
    4 5
    6 7
    8
    Рис. 9.8. Обработка запроса о сумме по диапазону с помощью двоичного индексного дерева
    А чтобы вычислить значение sum
    q
    (a, b), где a > 1, мы применим тот же прием, что в случае массивов префиксных сумм:
    sum
    q
    (a, b) = sum
    q
    (1, b) − sum
    q
    (1, a − 1).
    И sum
    q
    (1, b), и sum
    q
    (1, a − 1)можно вычислить за время O(log n), поэтому полная временная сложность равна O(log n).
    После изменения любого элемента массива необходимо обновить дво- ичное индексное дерево. Например, если изменяется элемент 3, то следует обновить суммы по диапазонам [3, 3], [1, 4] и [1, 8] (рис. 9.9). Поскольку каждый элемент массива принадлежит O(log n)такимдиапазонам, то до- статочно обновить O(log n) элементов дерева.
    1 4
    4 16 6
    7 4
    29 1
    2 3
    4 5
    6 7
    8
    Рис. 9.9. Обновление значения в двоичном индексном дереве
    Реализация. Операции двоичного индексного дерева эффективно реа- лизуются с помощью поразрядных операций. Нам понадобится следую щий факт: значение p(k)можно вычислить по формуле:
    p(k) = k & −k,
    которая выделяет самый младший единичный бит k.
    Следующая функция вычисляет sum
    q
    (1, k):
    int sum(int k) {
    int s = 0;

    150
    Глава 9. Запросы по диапазону
    while (k >= 1) {
    s += tree[k];
    k -= k&-k;
    }
    return s;
    }
    А эта функция увеличивает значение k-го элемента массивана x (x мо- жет иметь любой знак):
    void add(int k, int x) {
    while (k <= n) {
    tree[k] += x;
    k += k&-k;
    }
    }
    Временная сложность обеих функций равна O(log n), потому что они об- ращаются к O(log n)элементам двоичного индексного дерева, а переход к следующей позиции занимает время O(1).
    9.2.2. Деревья отрезков
    Дерево отрезков – это структура данных, предоставляющая две опера- ции с временной сложностью O(log n): обработка запроса по диапазону и изменение элемента массива. Деревья отрезков поддерживают запросы о сумме, о минимуме и многие другие. Своими корнями деревья отрез- ков уходят в геометрические алгоритмы (см., например, Bentley, Wood [4]), а элегантная восходящая реализация, представленная в этом разделе, взя- та из книги Stańczyk [34].
    Дерево отрезков – это двоичное дерево, в котором вершины нижнего уровня соответствуют элементам массива, а остальные несут информа- цию, необходимую для выполнения запросов по диапазону. При обсужде- нии деревьев отрезков мы будем предполагать, что размер массива – сте- пень двойки, и использовать индексирование, начиная с нуля, потому что для такого массива строить дерево отрезков удобнее. Если размер массива не является степенью двойки, то всегда можно добавить в конец элементы.
    Сначала обсудим деревья отрезков, поддерживающие запросы о сум- ме. На рис. 9.10 показаны массив и соответствующее ему дерево отрезков.
    Каждая внутренняя вершина дерева соответствует некоторому диапазо- ну массива, размер которого равен степени двойки. Для дерева отрезков, поддерживающего запросы о сумме, значение в каждой внутренней вер- шине равно сумме элементов соответствующего диапазона, и его можно вычислить как сумму значений в левой и правой дочерних вершинах.

    9.2. Древовидные структуры

    151
    5 8
    6 3
    2 7
    2 6
    0 1
    2 3
    4 5
    6 7
    5 8
    6 3
    2 7
    2 6
    13 9
    9 8
    22 17 39
    Рис. 9.10. Массив и соответствующее ему дерево отрезков для запросов о суммах
    Оказывается, что любой диапазон [a, b] можно разбить на O(log n)под- диапазонов, таких, что суммы их элементов уже хранятся в дереве от- резков. На рис. 9.11 показан диапазон [2, 7] в исходном массиве и в де- реве отрезков. В данном случае диапазону соответствуют две вершины и sum
    q
    (2, 7) = 9 + 17 = 26. Если при вычислении этой суммы используются вершины, расположенные как можно выше, то понадобится не более двух вершин на каждом уровне дерева. Поэтому общее число участвующих в вычислении вершин имеет порядок O(log n).
    5 8
    6 3
    2 7
    2 6
    0 1
    2 3
    4 5
    6 7
    5 8
    6 3
    2 7
    2 6
    13 9
    9 8
    22 17 39
    Рис. 9.11. Обработка запроса о сумме по диапазону с помощью дерева отрезков
    После изменения некоторого элемента массива необходимо обновить все вершины, значения в которых зависят от измененного элемента. Для

    152
    Глава 9. Запросы по диапазону этого следует пройти по пути от измененного элемента к корню дерева, обновляя все встретившиеся вершины. На рис. 9.12 показано, какие вер- шины обновляются после изменения пятого элемента. Путь от листа де- рева к корню содержит O(log n)вершин, поэтому при любом изменении обновляется O(log n)вершин.
    5 8
    6 3
    2 7
    2 6
    0 1
    2 3
    4 5
    6 7
    5 8
    6 3
    2 7
    2 6
    13 9
    9 8
    22 17 39
    Рис. 9.12. Как изменение элемента массива отражается на дереве отрезков
    Реализация. Хранить дерево отрезков удобно в массиве из 2n элемен- тов, где n – размер исходного массива. Вершины хранятся сверху вниз: tree
    [1] – корень, tree
    [2] и tree
    [3] – его дочерние вершины и т. д. Наконец, элементы tree
    [n
    ] по tree
    [2
    n − 1] соответствуют нижнему уровню дерева, на котором находятся элементы исходного массива. Отметим, что элемент tree
    [0] не используется.
    На рис. 9.13 показано, как хранится дерево, изображенное на рис. 9.11.
    Отметим, что родителем элемента tree
    [
    k] является tree
    [
    k/2⌋], его левым потомком – tree
    [2
    k], а правым потомком – tree
    [2
    k + 1]. Кроме того, любая вершина (кроме корня) занимает четную позицию, если это левый пото- мок своего родителя, и нечетную – если правый.
    39 22 17 13 9
    9 8
    5 8
    6 3
    2 7
    2 6
    1 2
    3 4
    5 6
    7 8
    9 10 11 12 13 14 15
    Рис. 9.13. Хранение дерева отрезков в виде массива
    Следующая функция вычисляет значение sum
    q
    (a, b):
    int sum(int a, int b) {
    a += n; b += n;
    int s = 0;

    9.2. Древовидные структуры

    153
    while (a <= b) {
    if (a%2 == 1) s += tree[a++];
    if (b%2 == 0) s += tree[b--];
    a /= 2; b /= 2;
    }
    return s;
    }
    Функция работает с диапазоном в массиве, представляющем дерево от- резков. Первоначально это диапазон [a + n, b + n]. На каждой итерации цикла диапазон поднимается на один уровень дерева выше, и значения в вершинах, не принадлежащих расположенному выше диапазону, прибав- ляются к сумме.
    Следующая функция увеличивает k-й элемент массива на x:
    void add(int k, int x) {
    k += n;
    tree[k] += x;
    for (k /= 2; k >= 1; k /= 2) {
    tree[k] = tree[2*k]+tree[2*k+1];
    }
    }
    Сначала обновляется значение на нижнем уровне, а затем – значения всех внутренних вершин дерева на пути до корня.
    Обе функции работают за время O(log n), поскольку дерево отрезков для массива с n элементами содержит O(log n)уровней, а та и другая функции на каждой итерации поднимаются на один уровень выше.
    Другие запросы. Деревья отрезков способны поддержать и другие за- просы по диапазону, при условии что диапазон можно разбить на две по- ловины, вычислить ответ для каждой части и эффективно объединить оба ответа. Примером может служить нахождение минимума и максимума, наибольшего общего делителя, а также поразрядные операции И, ИЛИ и
    ИСКЛЮЧАЮЩЕЕ ИЛИ.
    Так, дерево отрезков на рис. 9.14 поддерживает запросы о минимуме.
    В нем каждая вершина содержит наименьшее значение в соответствую- щем диапазоне массива. В корне дерева находится минимум по всему мас- сиву. Операции можно реализовать так же, как и выше, но вместо сумм вычисляются минимумы. Структура дерева отрезков также позволяет использовать для нахождения элементов массива метод двоичного поис- ка. Например, если дерево поддерживает запросы о минимуме, то можно найти позицию элемента с наименьшим значением за время O(log n). На рис. 9.15 показано, как элемент с наименьшим значением 1 ищется путем спуска по дереву, начиная с корня.

    154
    Глава 9. Запросы по диапазону
    5 8
    6 3
    1 7
    2 6
    5 3
    1 2
    3 1
    1 5
    8 6
    3 1
    7 2
    6 5
    3 1
    2 3
    1 1
    Рис. 9.14. Дерево отрезков для обработки запросов о минимуме по диапазону
    Рис. 9.15. Использование дво- ичного поиска для нахождения минимального элемента
    9.2.3. Дополнительные приемы
    Сжатие координат. В структурах данных, основанных на массивах, эле- менты индексируются последовательными целыми числами – и это мож- но считать ограничением. Трудности возникают, когда нужны большие индексы. Например, если требуется использовать индекс 10 9
    , то массив должен содержать 10 9
    элементов, для чего нужно слишком много памяти.
    Но если все индексы, которые могут понадобиться алгоритму, извест- ны заранее, то это ограничение можно обойти, воспользовавшись сжати-
    ем координат. Идея заключается в том, чтобы заменить исходные числа после довательными целыми числами 0, 1, 2 и т. д. Для этого мы опреде- лим функцию сжатия координат c. Она сопоставляет каждому исходному индексу i сжатый индекс c(i)таким образом, что если a и b – два числа и
    a < b, то c(a) < c(b). Сжатые индексы удобно использовать для выполнения запросов по диапазону.
    На рис. 9.16 приведен пример сжатия координат. Здесь реально исполь- зуются только индексы 2, 5 и 7, а все остальные элементы массива равны нулю. В результате сжатия остаются индексы c(2)= 0, c(5)= 1, c(7)= 2, что позволяет создать сжатый массив всего из трех элементов.
    0 0
    5 0
    0 3
    0 4
    0 1
    2 3
    4 5
    6 7
    original array
    5 3
    4 0
    1 2
    compressed array
    Исходный массив
    Сжатый массив
    Рис. 9.16. Сжатие массива с помощью сжатия индексов
    После сжатия координат мы можем, к примеру, построить дерево от- резков для сжатого массива и выполнять запросы. Нужно будет внести

    9.2. Древовидные структуры

    155
    только одну модификацию – сжимать координаты перед выполнением запроса: диа пазону [a, b] исходного массива соответствует диапазон [c(a),
    c(b)] сжатого.
    Обновление диапазона. До сих пор мы рассматривали структуры дан- ных, которые поддерживают запросы по диапазону и изменение одного значения. Теперь обратимся к противоположной ситуации: требуется об- новить диапазон и извлечь одно значение. Рассмотрим операцию увели- чения всех элементов в диапазоне [a, b] на x.
    Оказывается, что представленные в этой главе структуры данных мож- но использовать и для такой цели. Для этого построим разностный массив, в котором будут храниться разности между соседними элементами ис- ходного массива. Тогда исходный массив является массивом префиксных сумм для разностного. На рис. 9.17 показаны массив и соответствующий ему разностный массив. Так, значению 2 в шестой позиции исходного мас- сива соответствует сумма 3 − 2 + 4 − 3 = 2 в разностном массиве.
    3 3
    1 1
    1 5
    2 2
    0 1
    2 3
    4 5
    6 7
    original array
    3 0 −2 0 0
    4 −3 0 0
    1 2
    3 4
    5 6
    7
    difference array
    Исходный массив
    Разностный массив
    Рис. 9.17. Массив и соответствующий ему разностный массив
    У разностного массива имеется полезное свойство: чтобы изменить диа пазон исходного массива, достаточно изменить всего два элемента разностного. Точнее, чтобы увеличить все элементы в диапазоне [a, b] на
    x
    , достаточно увеличить элемент разностного массива в позиции a на x и уменьшить элемент в позиции b + 1 на x. Например, чтобы увеличить все элементы исходного массива с первого по четвертый на 3, мы прибавим 3 к первому элементу разностного массива и вычтем 3 из пятого элемента
    (рис. 9.18).
    3 6
    4 4
    4 5
    2 2
    0 1
    2 3
    4 5
    6 7
    original array
    3 3 −2 0 0
    1 −3 0 0
    1 2
    3 4
    5 6
    7
    difference array
    Исходный массив
    Разностный массив
    Рис. 9.18. Обновление диапазона массива с помощью разностного массива
    Таким образом, мы изменяем только отдельные значения в разностном массиве и выполняем для него запросы о сумме, следовательно, можно

    156
    Глава 9. Запросы по диапазону воспользоваться двоичным индексным деревом или деревом отрезков.
    Более трудная задача – придумать структуру данных, которая поддер- живала бы одновременно запросы по диапазону и обновления диапазона.
    В разделе 15.2.1 мы увидим, что это можно сделать с помощью ленивого дерева отрезков.

    1   ...   7   8   9   10   11   12   13   14   ...   26


    написать администратору сайта