1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8

# конспект · шаг 8 из 8

Конспект: дерево отрезков и дерево Фенвика

выжимка - её можно унести в заметки

Понятия

Дерево отрезков - листья = элементы, внутренний узел = СВЁРТКА своего отрезка (любая ассоциативная операция: сумма, min, max, gcd). Запрос [l, r] собирается из O(log n) узлов, целиком лежащих внутри диапазона (три случая на узле: вне / целиком внутри / частично); обновление элемента чинит O(log n) узлов на пути к корню. И запрос, и обновление - O(log n).

Префиксные суммы - плоская альтернатива: запрос O(1), но обновление O(n) (ломает все префиксы). Годятся только для СТАТИЧЕСКОГО массива.

Дерево Фенвика (BIT) - компактно для СУММ: один массив, ячейка i хранит блок длиной lowbit(i) = i & (−i). Префикс - складывать, СБРАСЫВАЯ младший бит (i −= lowbit); обновление - ПРИБАВЛЯЯ (j += lowbit); оба O(log n). Сумма [l,r] = префикс(r) − префикс(l−1). Только ОБРАТИМЫЕ операции (сумма, xor) - min/max не умеет. Индексация с 1.

Отложенные обновления (lazy) - дают дереву отрезков обновление ДИАПАЗОНА за O(log n): целиком накрытые узлы помечают меткой и правят свёртку оптом, не спускаясь к детям; метку проталкивают детям (push_down) по требованию. И запрос, и обновление делают push_down при спуске.

Неявный ключ - позиция элемента в последовательности, вычисленная по размерам левых поддеревьев; в узле она не хранится. split(t, k) - первые k элементов и остальные; merge(a, b) - склейка, где всё из a левее всего из b.

Сумма в неявном дереве: total = x + total(l) + total(r), у пустого узла 0. Два split выделяют [l, r), его корень хранит ответ; два merge возвращают части. Разворот сохраняет total, но меняет суммы по прежним позициям.

Алгоритм Мо - офлайн-ответ на запросы к отрезкам: сортировка по блоку ⌊l/√n⌋ и по r внутри блока, два указателя с пересчётом ответа за O(1) на шаг; O((n + q)·√n) шагов.

Что унести

Задача главы - диапазонный запрос И обновление за ЛОГАРИФМ, а не за n. Решение зависит от того, меняются ли данные и какая операция.

структура запрос обновление операции заметка
префиксные суммы O(1) O(n) обратимые только статика
дерево Фенвика O(log n) O(log n) суммы/xor компактно, просто
дерево отрезков O(log n) O(log n) любая ассоциативная универсально
+ lazy O(log n) диапазон O(log n) + обновление отрезка ленивые метки

Выбор: чисто чтения статического массива → префиксные суммы; динамические СУММЫ → Фенвик (проще, компактнее); min/max/gcd или сложные операции → дерево отрезков; обновление ДИАПАЗОНА → дерево отрезков с lazy.

Две сквозные идеи: (1) ИЕРАРХИЯ свёрток уравнивает запрос и обновление в O(log n), потому что элемент входит лишь в log n вложенных отрезков (развитие приёма дополнения); (2) ЛЕНИВОСТЬ - откладывать распространение изменений до востребования (как кеши, copy-on-write, растущий массив, splay).

Декартово дерево по неявному ключу - массив с правкой в середине за O(log n) в ожидании: вставка, удаление, перестановка кусков - по два-три split и merge; разворот отрезка - флаг в корне куска, проталкиваемый лениво. На ста тысячах элементов - 97 узлов на операцию против 41 тысячи сдвигов у списка.

Если ответ не складывается из половинок (число различных значений), дерево отрезков не годится; если запросы известны заранее, их переставляют по Мо: на 20 тысячах запросов - 2.4 миллиона шагов указателей против 230 миллионов.

Ловушки джокера

Префиксные суммы при обновлениях - каждое обновление O(n). Для динамики - дерево/Фенвик.

В запросе дерева отрезков спускаться в целиком-внутренние узлы - их свёртка готова, углубление теряет O(log n).

Забыть починить свёртки вверх после обновления листа - запросы соврут.

Фенвик для min/max - не работает (нет вычитания). Только обратимые операции.

Фенвик с индекса 0 - lowbit(0)=0 зацикливает. Индексация с 1.

Забыть push_down в ЗАПРОСЕ при lazy - прочитаешь устаревшие свёртки детей.

Перезаписать метку lazy вместо КОМБИНИРОВАНИЯ - потеряешь прежнее отложенное изменение.

Забыл пересчитать size или протолкнуть флаг. Ошибки не будет - split молча режет не там, и последовательность тихо становится другой.

Применил Мо к онлайн-запросам или выдал ответы в его порядке. Мо работает только офлайн, а ответы возвращают по номеру исходного запроса.

Проверь себя

Почему запрос дерева отрезков - O(log n), а не O(n)?

Чем дерево отрезков лучше префиксных сумм и когда лучше префиксы?

Что хранит ячейка Фенвика и как собирается префиксная сумма?

Чем Фенвик ограничен по сравнению с деревом отрезков?

Как отложенные метки дают обновление диапазона за O(log n) и почему это не портит данные?

Как без хранения позиций найти k-й элемент декартова дерева и разрезать его по позиции?

В каком порядке алгоритм Мо обходит запросы и откуда берётся оценка (n + q)·√n?

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.