Спроектируйте структуру данных для поддержки динамического набора интервалов с операциями вставки, удаления и запроса суммы покрытий на отрезке за логарифмическое время — опишите используемые компоненты, инварианты и сложность операций
Коротко — надёжный путь: сегментное дерево с подсчётом покрытий (lazy count) над массивом элементарных отрезков, полученных либо сжатием координат, либо имплицитно на фиксированном диапазоне. Ниже — компоненты, инварианты, операции и сложности. Компоненты - Массив уникальных координат (при сжатии): X[0..N]X[0..N]X[0..N] отсортирован, элементарный отрезок iii = [X[i],X[i+1))[X[i],X[i+1))[X[i],X[i+1)). - Узел сегдерева, покрывающий индексный диапазон [L,R)[L,R)[L,R) элементарных отрезков, с полями: - cntcntcnt — целочисленный счётчик прямого покрытия этого узла (сколько интервалов полностью покрывают весь узел). - lenlenlen — суммарная длина покрытой части в этом узле (в единицах координат). - указатели/индексы на левый и правый дочерние узлы. - Для имплицитного дерева вместо массива XXX хранится числовой диапазон [A,B)[A,B)[A,B) и в узле — длина сегмента B−AB-AB−A. Инварианты 1. cnt≥0cnt \ge 0cnt≥0. Если cnt>0cnt>0cnt>0, то весь сегмент узла полностью покрыт и len=len =len= длина сегмента. 2. Если cnt=0cnt=0cnt=0, то len=left.len+right.lenlen = left.len + right.lenlen=left.len+right.len (для листа — len=0len=0len=0 если нет покрытия). 3. Узел всегда отражает суммарную покрытую длину своих элементарных отрезков. Операции - Вставка интервала [l,r)[l,r)[l,r): 1. Привести l,rl,rl,r к индексам элементарных отрезков (сжатие) либо работать с числовым диапазоном. 2. Вызвать update(node, ql, qr, delta=+1): при полном покрытии узла увеличить cntcntcnt на +1+1+1; затем recompute lenlenlen: если cnt>0cnt>0cnt>0 то len=len=len= длина сегмента, иначе len=left.len+right.lenlen=left.len+right.lenlen=left.len+right.len. - Удаление интервала [l,r)[l,r)[l,r): аналогично вставке, но с delta=−1delta=-1delta=−1. (Нужно гарантировать удаление только существующей копии; для этого храните multiset/хэш по id интервала.) - Запрос суммы покрытия на отрезке [a,b)[a,b)[a,b): 1. Привести к индексам и выполнить query(node, ql, qr): если узел полностью внутри запроса вернуть lenlenlen; если частично — рекурсивно суммировать результаты дочерей при учёте cntcntcnt (но по инварианту при cnt>0cnt>0cnt>0 узел уже полностью покрыт). Реализация основных правил обновления (push-up): - При входе в узел после изменения детей: - если cnt>0cnt>0cnt>0 тогда len=len =len= длина сегмента узла; - иначе len=left.len+right.lenlen = left.len + right.lenlen=left.len+right.len (если лист и cnt=0cnt=0cnt=0 — len=0len=0len=0). Сложности - Пусть NNN — число элементарных отрезков (после сжатия), либо UUU — размер числового диапазона для имплицитного дерева. - Время вставки/удаления/запроса: O(logN)O(\log N)O(logN) (со стандартным бинарным деревом) или O(logU)O(\log U)O(logU) для имплицитного сегдерева. - Память: O(N)O(N)O(N) для полного дерева, или O(k)O(k)O(k) для динамически выделяемого дерева (где kkk — число созданных узлов). - Построение массива координат (сжатие) из mmm операций: O(mlogm)O(m\log m)O(mlogm); после этого каждая операция — O(logN)O(\log N)O(logN). Замечания и практические детали - Используйте представление полуинтервалов [l,r)[l,r)[l,r) для однозначности. Для включительно/исключительно конвертируйте вручную. - Для удаления по идентификатору храните хэш-таблицу, сопоставляющую id интервала его (l,r)(l,r)(l,r) в сжатых индексах. - Если нужна строго гарантия O(logn)O(\log n)O(logn) по числу интервалов nnn и без известного набора координат, можно использовать динамический сегдерев (implicit) на диапазоне значений типа 64-бит; время будет O(logU)O(\log U)O(logU), где UUU — диапазон координат (например 2642^{64}264). Итого: сегментное дерево с полем счётчика покрытий и хранением длины покрытой части обеспечивает вставку, удаление и запрос суммы покрытий за O(logN)O(\log N)O(logN) при сжатых координатах (или O(logU)O(\log U)O(logU) для имплицитного).
Компоненты
- Массив уникальных координат (при сжатии): X[0..N]X[0..N]X[0..N] отсортирован, элементарный отрезок iii = [X[i],X[i+1))[X[i],X[i+1))[X[i],X[i+1)).
- Узел сегдерева, покрывающий индексный диапазон [L,R)[L,R)[L,R) элементарных отрезков, с полями:
- cntcntcnt — целочисленный счётчик прямого покрытия этого узла (сколько интервалов полностью покрывают весь узел).
- lenlenlen — суммарная длина покрытой части в этом узле (в единицах координат).
- указатели/индексы на левый и правый дочерние узлы.
- Для имплицитного дерева вместо массива XXX хранится числовой диапазон [A,B)[A,B)[A,B) и в узле — длина сегмента B−AB-AB−A.
Инварианты
1. cnt≥0cnt \ge 0cnt≥0. Если cnt>0cnt>0cnt>0, то весь сегмент узла полностью покрыт и len=len =len= длина сегмента.
2. Если cnt=0cnt=0cnt=0, то len=left.len+right.lenlen = left.len + right.lenlen=left.len+right.len (для листа — len=0len=0len=0 если нет покрытия).
3. Узел всегда отражает суммарную покрытую длину своих элементарных отрезков.
Операции
- Вставка интервала [l,r)[l,r)[l,r):
1. Привести l,rl,rl,r к индексам элементарных отрезков (сжатие) либо работать с числовым диапазоном.
2. Вызвать update(node, ql, qr, delta=+1): при полном покрытии узла увеличить cntcntcnt на +1+1+1; затем recompute lenlenlen: если cnt>0cnt>0cnt>0 то len=len=len= длина сегмента, иначе len=left.len+right.lenlen=left.len+right.lenlen=left.len+right.len.
- Удаление интервала [l,r)[l,r)[l,r):
аналогично вставке, но с delta=−1delta=-1delta=−1. (Нужно гарантировать удаление только существующей копии; для этого храните multiset/хэш по id интервала.)
- Запрос суммы покрытия на отрезке [a,b)[a,b)[a,b):
1. Привести к индексам и выполнить query(node, ql, qr): если узел полностью внутри запроса вернуть lenlenlen; если частично — рекурсивно суммировать результаты дочерей при учёте cntcntcnt (но по инварианту при cnt>0cnt>0cnt>0 узел уже полностью покрыт).
Реализация основных правил обновления (push-up):
- При входе в узел после изменения детей:
- если cnt>0cnt>0cnt>0 тогда len=len =len= длина сегмента узла;
- иначе len=left.len+right.lenlen = left.len + right.lenlen=left.len+right.len (если лист и cnt=0cnt=0cnt=0 — len=0len=0len=0).
Сложности
- Пусть NNN — число элементарных отрезков (после сжатия), либо UUU — размер числового диапазона для имплицитного дерева.
- Время вставки/удаления/запроса: O(logN)O(\log N)O(logN) (со стандартным бинарным деревом) или O(logU)O(\log U)O(logU) для имплицитного сегдерева.
- Память: O(N)O(N)O(N) для полного дерева, или O(k)O(k)O(k) для динамически выделяемого дерева (где kkk — число созданных узлов).
- Построение массива координат (сжатие) из mmm операций: O(mlogm)O(m\log m)O(mlogm); после этого каждая операция — O(logN)O(\log N)O(logN).
Замечания и практические детали
- Используйте представление полуинтервалов [l,r)[l,r)[l,r) для однозначности. Для включительно/исключительно конвертируйте вручную.
- Для удаления по идентификатору храните хэш-таблицу, сопоставляющую id интервала его (l,r)(l,r)(l,r) в сжатых индексах.
- Если нужна строго гарантия O(logn)O(\log n)O(logn) по числу интервалов nnn и без известного набора координат, можно использовать динамический сегдерев (implicit) на диапазоне значений типа 64-бит; время будет O(logU)O(\log U)O(logU), где UUU — диапазон координат (например 2642^{64}264).
Итого: сегментное дерево с полем счётчика покрытий и хранением длины покрытой части обеспечивает вставку, удаление и запрос суммы покрытий за O(logN)O(\log N)O(logN) при сжатых координатах (или O(logU)O(\log U)O(logU) для имплицитного).