Дерево отрезков с групповыми операциями
Оглавление
В разделе дерева отрезков мы научились решать две похожие задачи:
- сумма на отрезке и обновление одного элемента
- добавление на отрезке и вычисление значения на позиции
Но что делать, если мы хотим добавить на отрезке и сумму на отрезке? Тогда нам поможет дерево отрезков с групповыми операциями. Идея заключается в том, что мы будем хранить не только сумму на отрезке, но и “ленивую” информацию о том, сколько мы хотим добавить к каждому элементу на отрезке.
1
2
3
4
5
6
struct Node {
int sum; // сумма на отрезке
int add; // сколько мы хотим добавить к каждому элементу на отрезке
};
Node t[2 * N]; // массив, в котором мы будем хранить дерево отрезков
Как поменяется реализация?
Оказыватся, почти никак! Всё что нам нужно - научиться вовремя проталкивать ленивую информацию вниз по дереву - каждый раз перед тем как посетить вершину. Иначе говоря, если мы находимся в вершине $v$ и у нас есть информация о том, сколько мы хотим добавить к каждому элементу на отрезке, то мы должны обновить сумму на отрезке и добавить информацию к левому и правому поддереву. Для этого нам нужно создать функцию push
, которая и будет этим заниматься:
1
2
3
4
5
6
7
8
9
10
void push(int v, int l, int r) { // функция, которая будет "проталкивать" информацию вниз
if (t[v].add != 0) { // если у нас есть информация о том, сколько мы хотим добавить к каждому элементу на отрезке
if (r - l > 1) { // если мы не находимся в листе
t[left(v)].add += t[v].add; // добавляем информацию к левому поддереву
t[right(v)].add += t[v].add; // добавляем информацию к правому поддереву
}
t[v].sum += (r - l) * t[v].add; // обновляем сумму на отрезке
t[v].add = 0; // обнуляем информацию о том, сколько мы хотим добавить к каждому элементу на отрезке
}
}
Все запросы на сумму и добавление на отрезке будут выглядеть почти так же, как и раньше. Но теперь нам нужно вызывать функцию push
, чтобы актуализировать информацию о вершине и о её детях.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int sum(int v, int l, int r, int ql, int qr) {
push(v, l, r); // +1 строка
if (ql >= r || qr <= l) {
return 0;
} else if (ql <= l && qr >= r) {
return t[v].sum;
} else {
int m = (l + r) / 2;
return sum(left(v), l, m, ql, qr) + sum(right(v), m, r, ql, qr);
}
}
void add(int v, int l, int r, int ql, int qr) {
push(v, l, r); // +1 строка
if (ql >= r || qr <= l) {
return;
} else if (ql <= l && qr >= r) {
t[v].add += x;
push(v, l, r); // +2 строка
} else {
int m = (l + r) / 2;
add(left(v), l, m, ql, qr);
add(right(v), m, r, ql, qr);
t[v].sum = t[left(v)].sum + t[right(v)].sum;
}
}
Таким образом, реализация отличается от обычного дерева отрезков лишь тремя дополнительными вызовами push (не забывайте вызывать в add в ДВУХ местах)!
Задачи
- На плоскости дано $n$ прямоугольников, заданных координатами левого нижнего и правого верхнего углов. Нужно научиться вычислять площадь объединения прямоугольников за $O(n \log n)$.
- На плоскости даны $n$ прямоугольников. Поступают запросы вида “принадлежит ли точка хотя бы одному прямоугольнику”. Нужно научиться отвечать на запросы за $O(\log n)$.