Изменения

Перейти к: навигация, поиск

Корневая декомпозиция с операциями: get, insert, erase

1315 байт добавлено, 22:30, 17 января 2017
Нет описания правки
'''Корневая декомпозиция ''' {{---}} это подход к реализации ассоциативных операций (напримерс операциями удаления, суммирование элементов, нахождение минимума/максимума и т.д.) над идущими подряд элементами некоторого множества размера <tex>n</tex> за <tex> O(\sqrt n)</tex>. Так же есть поддержка операций удаления и вставки в произвольное местои вычисления функции на отрезке.
== Идея Описание модификации исходной структуры ==
Пусть дан массив <math>A</math> размерности <math>n</math> и <math>m</math> запросов вида: посчитать сумму чисел на отрезке, вставить элемент в любую позицию, удалить любой элемент.
Будем поддерживать разбиение массива <math>A</math> на блоки. Введем операцию <math>\mathrm{split}</math>, которая позволяет изменить структуру и разрезать один блок на два других. Такая операция увеличит количество блоков на <math>O(1)</math>. Введем вспомогательную функцию <math>\mathrm{rebuild}</math>. Данная функция позволяет вновь перестроить структуру.
Реализуем две основные операции <math>\mathrm{split}</math> и <math>\mathrm{rebuild}</math>. Остальные операции выразим через них.Пусть время работы операции составляет <math>\mathrm{split}</math> <math>O(cnt + len)</math> и операции <math>\mathrm{rebuild}</math> составляет <math>O(|A| + cnt)</math>, где <math>len</math> {{- --}} длина блоков, <math>cnt</math> {{--- }} количество блоков. Ниже будет приведена реализация этих функций с такой асимптотикой
Выберем <math>len = \sqrt n</math>, тогда после операции <math>build</math> будет <math>cnt = O(\sqrt n)</math> блоков, где <math>n</math> текущие количество элементов в структуре. Заметим, что любая операция добавляет не более, чем <math>4</math> новых блоков. После каждых <math>k</math> операций вызовем <math>\mathrm{rebuild}</math>.
Асимптотикавыполнения <math>m</math> запросов составляет: <math>O((\dfrac m / k) \cdot (n + 4 \cdot k) + m \cdot (len + 4 \cdot k))</math>.
Найдем минимум функции, который достигается при <math>k = O(\sqrt n)</math>.
Итоговое время работы: <math>O(\sqrt n \cdot (n + m))</math>.
 
 
== Построение ==
<noinclude>Задача:{{Задача|definition = Пусть дан массив <math>A</math> размерности <math>n</math> и <math>m</math> запросов вида: добавить элемент <math>y</math> после элемента <math>x</math>, удалить элемент с индексом <math>x</math>, посчитать сумму на отрезке <math>[l, r]</math>. }}</noinclude><includeonly>{{#if: {{{neat|}}}|<div style="background-color: #fcfcfc; float:left;"><div style="background-color: #ddd;">'''Задача:'''</div><div style="border:1px dashed #2f6fab; padding: 8px; font-style: italic;">{{{definition}}}</div></div>|<table border="0" width="100%"><tr><td style="background-color: #ddd">'''Задача:'''</td></tr><tr><td style="border:1px dashed #2f6fab; padding: 8px; background-color: #fcfcfc; font-style: italic;">{{{definition}}}</td></tr></table>}}</includeonly> Cделаем следующие действия:* разделим массив <math>A</math> на блоки длины <math>len = \lfloor \sqrt{n} \rfloor</math>,* в каждом блоке заранее посчитаем необходимую операцию,* для каждого блока сохраним индекс самого левого и самого правого элемента в массивах <math>L, R</math>,* результаты подсчета запишем в массив <math>B</math> размерности <math>cnt</math>, где <math>cnt = \left\lceil \dfrac{n}{len} \right\rceil</math> {{---}} количество блоков,* заведем массив <math>T</math>, в котором храним актуальный порядок блоков. <math>T = [0, 1, ... \ldots, cnt - 1]</math> соответствует порядку <math>B_0, B_1 , \ldots , B_{cnt-1}</math>.
Пример реализации построения для операции <math> + </math>:
<code>
'''void''' build():
'''for''' i = 0 ... n - 1 T[i] = i L[i] = i * len R[i] = (i + 1) * len - 1 '''for''' i = 0 ... n - 1 B[i / len] = B[i / len] + A[i]
</code>
Построение, происходит за <tex>O(n)</tex> времени.
== Операций Операция <math>\mathrm{split }</math>==Данная операций нужна для реализации операций: <math>\mathrm{insert}</math>, <math>\mathrm{erase}</math>, <math>\mathrm{get}</math>. Она позволяет разделить один блок на два других с целью создания разреза, который необходим в других операциях. Индекс <math>i</math> называется разрезом, если не существует такого актуального блока, которому принадлежит индекс <math>i</math> и <math>i + 1</math> одновременно.
Пусть получен запрос на выполнение операции для индекса <math>x</math>. Этот индекс входит ровно в один блок массива <math>B</math>, пусть индекс этого блока – <math>ind</math>. После операции <math>\mathrm{split}</math> этот блок поделится на <math>[L[ind], x]</math> и <math>[x + 1, R[ind]]</math>, если <math>x</math> был в середине блока, иначе <math>x</math> уже является разрезом и никакая операций не требуется. Как мы видим после операции у нас появилось максимум два новых блока.
Для удобства реализации в массивы <math>B, L, R</math> можно только дописывать новую информацию в конце, что соответствует созданию новых блоков.
<code>
'''int''' createNewBlock('''int''' l, '''int''' r): <font color=green>// Вспомогательная функция. Позволяет создать блок с <math>l</math> по <math>r</math>.</font> result = 0 '''for''' i = l ... r result = result + A[i] B.push_backpushBack(result) L.push_backpushBack(l) R.push_backpushBack(r) cnt++ '''return ''' cnt - 1
</code>
<code>
'''int''' split('''int''' x):
ind = 0 '''for''' i = 0 ... |T| - 1 '''if ''' L[i] <= x '''and ''' x <= R[i] ind = i '''if ''' L[ind] == x '''or ''' R[ind] == x '''or ''' x < 0 '''return ''' 0 first = createNewBolock(L[ind], x) second = createNewBolock(x, R[ind]) T.erase(ind) <font color=green>// операций T.erase(x) удаляет элемент под номером x и сдвигает массив T. Время работы <math>O(|T|)</math> </font> T.insert(ind. first) <font color=green>// операций T.insert(x, y) вставляет в массив T после индекса x значение y и сдвигает массив. Время работы <math>O(|T|)</math> </font> T.insert(ind + 1, second) '''return ''' ind
</code>
Асимптотика: <math>O(cnt)</math> – поиск нужного блока, <math>O(len)</math> – подсчет функции. Итог: <math>O(len + cnt)</math>.Так же Также заметим, что мы увеличили <math>cnt</math> на <math>2</math>.
== Операций Операция <math>\mathrm{rebuild }</math>==Вторая необходимая операция – это {{---}} <math>\mathrm{rebuild}</math>. Заметим, что после операций <math>\mathrm{split}</math> количество блоков увеличивалось, а работа всех функций зависит от этого числа. Для того чтоб <math>cnt</math> не стало слишком большим будем полностью перестраивать структуру изменяя <math>cnt</math> на базовое значение равное <math>cnt = \left\lceil \dfrac{n}{len} \right\rceil</math>.
Будем восстанавливать из актуальных блоков массив <math>A</math>. Потом очищать все текущие блоки, а затем вызывать операцию <math>build</math> для построение новой структуры.
<code>
'''void''' rebuild():
tempA <font color=green>// временная актуальная копия массива <math>A</math> </font> '''for''' i = 0 ... |T| - 1 '''for''' j = L[i] ... R[i] tempA.push_backpushBack(A[j]) A = tempA B.clear() L.clear() R.clear() build()
</code>
Асимптотика: заметим, оба циклам суммарно запишут ровно столько элементов, сколько их было в структуре. <math>O(|A| + cnt)</math>.
== Операций Операция <math>\mathrm{get }</math>==
Пусть получен запрос на выполнение операции на отрезке <math>[l, r]</math>. Будем выполнять операции только на целых блоках, изменим нашу структуры так, чтобы граница отрезка никогда не попадал в середину блока.
Перейдем к реализации:
* Разделим наши блоки при помощи операции <math>\mathrm{split}</math>
* Посчитаем операцию на целых блоках использую массив <math>B</math>
<code>
'''int''' get('''int''' l, '''int''' r):
result = 0 indexL = split(l – 1) indexR = split(r) – 1 '''for ''' i = indexL ... indexR result = result + B[T[i]] '''return ''' result
</code>
Асимптотика: <math>O(split)</math> и <math>O(|T|)</math>. Итого: <math>O(cnt + len)</math>.
== Операция <math>\mathrm{erase}</math>==
Пусть получен запрос на выполнение операции удаления числа на позиции <math>x</math>. Аналогично операции <math>get</math>, мы не хотим удалять из середины блока. Когда <math>x</math> является единственным числом в блоке, мы можем просто удалить его из массива <math>T</math>.
Перейдем к реализации:
* Разделим наши блоки при помощи операции <math>\mathrm{split}</math>
* Посчитаем операцию на целых блоках использую массив <math>B</math>
<code>
'''void''' erase('''int''' x):
split(x - 1) ind = split(x) T.erase(ind)
</code>
Асимптотика: <math>O(cnt + len)</math>.
== Операция <math>\mathrm{insert}</math>==
Пусть получен запрос на выполнение вставить число <math>y</math> после числа с индексом <math>x</math>. Аналогично операции <math>get</math>, мы не хотим вставлять в середину блока. Когда нужно вставить на границу блока, то мы можем просто добавить число <math>x</math> в конец массива <math>A</math> и создать новый блок размер <math>1</math>, который ссылается на это число.
Перейдем к реализации:
* Разделим наши блоки при помощи операции <math>\mathrm{split}</math>
* Добавим в конец <math>A</math> число <math>y</math>
* Создадим новый блок и вставим в нужное место
<code>
'''void''' insert('''int''' x, '''int''' y):
ind = split(x) A.push_backpushBack(y) indexNewBlock = createNewBlock(|A| - 1, |A| - 1) T.insert(ind, indexNewBlock)
</code>
Асимптотика: <math>O(cnt + len)</math>.
==См. также==
* [[Многомерное дерево отрезков]]
* [[Статистики на отрезках. Корневая эвристика]]
 
[[Категория: Дискретная математика и алгоритмы]]
[[Категория: Дерево отрезков]]
7
правок

Навигация