Несогласованные поддеревья. Реализация массового обновления
Дерево отрезков позволяет осуществлять так называемые массовые операций, то есть данная структура позволяет выполнять операций с несколькими подряд идущими элементами. Причем время работы, как и при других запросах, равно
.Несогласованные поддеревья
Сперва рассмотрим так называемые несогласованные поддеревья.
В несогласованном поддереве дерева отрезков в вершинах хранятся не истинные значения сумм (по операции
) на отрезках, однако гарантируется, что на запрос они отвечают верно. При этом в корне поддерева, которому соответствует отрезок хранится несогласованность . Если в вершине хранится истинное значение суммы, то — нейтральный элемент относительно операции (например 0 для прибавления). Для реализации вторая операция должна быть ассоциативной, и операций должны удовлетворять свойству дистрибутивности:Массовое обновление
Рассмотрим в общем виде реализацию массовой операций на отрезке. Пусть необходимо отвечать запросы относительно операций
, а запрос массового обновления идет по операций .Для эффективной реализаций будем использовать описанную выше структуру — несогласованные поддеревья. В каждой вершине, помимо непосредственно результата выполнения операций
, храним несогласованность — величина, с которой нужно выполнить операцию для всех элементов текущего отрезка. Тем самым мы сможем обрабатывать запрос массового обновления на любом подотрезке эффективно, вместо того чтобы изменять все значений. Как известно из определения несогласованных поддеревьев, в текущий момент времени не в каждой вершине дерева хранится истинное значение, однако когда мы обращаемся к текущему элементу мы работаем с верными данными. Это обеспечивается так называемым "проталкиванием" несогласованности детям при каждом обращений к текущей вершине. При этом после обращения к вершине необходимо пересчитать значение по операций , так как значение в детях могло измениться.Таким образом необходимо во-первых не забыть раздать детям несогласованность, во-вторых вызвать функцию от детей и, в-третьих, пересчитать свое значение. Очень важно выполнить все три пункта.
Пример
Рассмотрим массовые операции на отрезке на примере задачи "Прибавление на отрезке". При этом мы должны отвечать на запрос минимума на отрезке.
Следуя вышеописанному алгоритму будем в каждой вершине хранить минимум на текущем отрезке и несогласованность — сколько необходимо прибавить ко всем числам этого отрезка(соответственно при запросе минимума истинный минимум на отрезке при корректной несогласованности — сумма несогласованности и значения в вершине). При этом не забудем выполнять "проталкивание" несогласованности и делать обновление минимума на текущем отрезке.
Ниже приведен код данного алгоритма.
Псевдокод
Используется классическая реализация дерева отрезка с полуинтервалами.
Пусть в узлах дерева хранятся структуры из четырех полей:
- — левая граница полуинтервала, за который "отвечает" текущая вершина.
- — правая граница этого полуинтервала.
- — минимум на полуинтервале.
- — несогласованность.
// Процедура "проталкивания" несогласованности детям void push(int node) { tree[2 * node + 1].d += tree[node].d; tree[2 * node + 2].d += tree[node].d; tree[node].d = 0; } int get_min(int node, int a, int b) { // node - текущая вершина, a и b - границы запроса l = tree[node].left; r = tree[node].right; if [l, r)[a, b) == return ; if [l, r) == [a, b) return tree[node].min + tree[node].d; push(node); int m = (l + r) / 2; int ans = min(get_min (node * 2 + 1, a, min(b, m)), get_min (node * 2 + 2, max(a, m), b))); // Пересчитываем свое значение tree[node].min = min(tree[2 * node + 1].min + tree[2 * node + 1].d, tree[2 * node + 2].min + tree[2 * node + 2].d); return ans; } void update(int node, int a, int b, int val) { // val - значение, на которое нужно увеличить отрезок l = tree[node].left; r = tree[node].right; if [l, r) [a, b) == return; if [l, r) == [a, b) tree[node].d += val; return; push(node); // Вызываем обновление детей update(2 * node + 1, a, b, val); update(2 * node + 2, a, b, val); tree[node].min = min(tree[2 * node + 1].min + tree[2 * node + 1].d, tree[2 * node + 2].min + tree[2 * node + 2].d); }
Псевдокод в общем виде
Используется классическая реализация дерева отрезка с полуинтервалами.
Пусть в узлах дерева хранятся структуры из четырех полей:
- — левая граница полуинтервала, за который "отвечает" текущая вершина.
- — правая граница этого полуинтервала.
- — минимум на полуинтервале.
- — несогласованность.
// Процедура "проталкивания" несогласованности детям void push(int node) { tree[2 * node + 1].d += tree[node].d; tree[2 * node + 2].d += tree[node].d; tree[node].d = 0; } int get_min(int node, int a, int b) { // node - текущая вершина, a и b - границы запроса l = tree[node].left; r = tree[node].right; if [l, r)[a, b) == return ; if [l, r) == [a, b) return tree[node].min + tree[node].d; push(node); int m = (l + r) / 2; int ans = min(get_min (node * 2 + 1, a, min(b, m)), get_min (node * 2 + 2, max(a, m), b))); // Пересчитываем свое значение tree[node].min = min(tree[2 * node + 1].min + tree[2 * node + 1].d, tree[2 * node + 2].min + tree[2 * node + 2].d); return ans; } void update(int node, int a, int b, int val) { // val - значение, на которое нужно увеличить отрезок l = tree[node].left; r = tree[node].right; if [l, r) [a, b) == return; if [l, r) == [a, b) tree[node].d += val; return; push(node); // Вызываем обновление детей update(2 * node + 1, a, b, val); update(2 * node + 2, a, b, val); tree[node].min = min(tree[2 * node + 1].min + tree[2 * node + 1].d, tree[2 * node + 2].min + tree[2 * node + 2].d); }