Дерево отрезков (Segment Tree) — это структура данных для эффективного выполнения запросов на отрезках массива: суммы, минимума, максимума и других ассоциативных операций.
Мотивация
Пусть есть массив $a[0], a[1], \ldots, a[n-1]$, и нужно поддерживать две операции:
Запрос на отрезке: найти сумму/минимум/максимум на отрезке $[l, r]$
Обновление элемента: изменить значение $a[i]$
Структура
Запрос на отрезке
Обновление
Массив
$O(n)$
$O(1)$
Префиксные суммы
$O(1)$
$O(n)$
Дерево отрезков
$O(\log n)$
$O(\log n)$
Дерево отрезков обеспечивает логарифмическое время для обеих операций.
Структура дерева отрезков
Определение
Дерево отрезков — это полное бинарное дерево, в котором:
Листья соответствуют элементам исходного массива
Каждая внутренняя вершина хранит агрегированное значение (сумму, минимум и т.д.) для отрезка, соответствующего её поддереву
defquery(self, l, r):
"""Сумма на отрезке [l, r]"""return self._query(1, 0, self.size -1, l, r)
def_query(self, node, node_left, node_right, l, r):
"""Рекурсивный запрос"""# Отрезок запроса не пересекается с отрезком узлаif r < node_left or l > node_right:
return0# Отрезок запроса полностью содержит отрезок узлаif l <= node_left and node_right <= r:
return self.tree[node]
# Частичное пересечение mid = (node_left + node_right) //2 left_sum = self._query(2* node, node_left, mid, l, r)
right_sum = self._query(2* node +1, mid +1, node_right, l, r)
return left_sum + right_sum
# Итеративная версия (быстрее):# Идея: l и r — указатели на листья в массиве tree[size..2*size-1].# На каждом шаге поднимаемся на уровень вверх (l //= 2, r //= 2).# Если l — правый ребёнок (l % 2 == 1), его родитель покрывает# лишние элементы слева, поэтому берём tree[l] отдельно и сдвигаем l вправо.# Аналогично для r: если r — левый ребёнок (r % 2 == 0), берём tree[r]# и сдвигаем r влево. Так на каждом уровне O(1) работы, всего O(log n).defquery_iterative(self, l, r):
"""Итеративный запрос суммы на [l, r]""" l += self.size
r += self.size
result =0while l <= r:
if l %2==1:
result += self.tree[l]
l +=1if r %2==0:
result += self.tree[r]
r -=1 l //=2 r //=2return result
classMinSegmentTree:
def__init__(self, data):
self.n = len(data)
self.size =1while self.size < self.n:
self.size *=2 self.tree = [float('inf')] * (2* self.size)
for i in range(self.n):
self.tree[self.size + i] = data[i]
for i in range(self.size -1, 0, -1):
self.tree[i] = min(self.tree[2* i], self.tree[2* i +1])
defquery_min(self, l, r):
"""Минимум на отрезке [l, r]""" l += self.size
r += self.size
result = float('inf')
while l <= r:
if l %2==1:
result = min(result, self.tree[l])
l +=1if r %2==0:
result = min(result, self.tree[r])
r -=1 l //=2 r //=2return result
defupdate(self, index, value):
index += self.size
self.tree[index] = value
while index >1:
index //=2 self.tree[index] = min(self.tree[2* index], self.tree[2* index +1])
#include<vector>#include<algorithm>#include<climits>classMinSegmentTree {
private: std::vector<int> tree;
int n;
int size;
public: MinSegmentTree(const std::vector<int>& data) {
n = data.size();
size =1;
while (size < n) size *=2;
tree.assign(2* size, INT_MAX);
for (int i =0; i < n; i++) {
tree[size + i] = data[i];
}
for (int i = size -1; i >0; i--) {
tree[i] = std::min(tree[2* i], tree[2* i +1]);
}
}
intqueryMin(int l, int r) {
l += size;
r += size;
int result = INT_MAX;
while (l <= r) {
if (l %2==1) {
result = std::min(result, tree[l]);
l++;
}
if (r %2==0) {
result = std::min(result, tree[r]);
r--;
}
l /=2;
r /=2;
}
return result;
}
voidupdate(int index, int value) {
index += size;
tree[index] = value;
while (index >1) {
index /=2;
tree[index] = std::min(tree[2* index], tree[2* index +1]);
}
}
};
Обобщённое дерево отрезков
Дерево отрезков работает для любой ассоциативной операции (операции, для которой $(a \circ b) \circ c = a \circ (b \circ c)$):
Сумма: $a + b$
Минимум: $\min(a, b)$
Максимум: $\max(a, b)$
Произведение: $a \cdot b$
НОД: $\gcd(a, b)$
Побитовое И/ИЛИ/XOR
Определение
Для обобщённого дерева отрезков нужна нейтральный элемент $e$, такой что $a \circ e = e \circ a = a$: