Участник:Artem.ustinov/НВП — различия между версиями
(Новая страница: «{{Задача |definition = Дана перестановка <tex>\pi</tex> множества<tex>~\{1, 2,~\dots,~n\}</tex>. Требуется найти [[З...») |
(→Основная идея) |
||
| Строка 13: | Строка 13: | ||
Для каждой длины <tex>l = 1, 2,~\dots,~n</tex> предполагаемой НВП находим наименьший элемент, который может быть последним в возрастающей подпоследовательности длины <tex>l</tex> и запишем его в массив <tex>B_l</tex>. Будем называть его наилучшим элементом для длины <tex>l</tex>. | Для каждой длины <tex>l = 1, 2,~\dots,~n</tex> предполагаемой НВП находим наименьший элемент, который может быть последним в возрастающей подпоследовательности длины <tex>l</tex> и запишем его в массив <tex>B_l</tex>. Будем называть его наилучшим элементом для длины <tex>l</tex>. | ||
| + | * Если <tex>\pi_i</tex> больше каждого элемента <tex>B</tex>, вычисленного для подпоследовательности <tex>\pi_1, \pi_2,~\dots~,\pi_{i-1}</tex>, значит с ним можно сделать возрастающую подпоследовательность максимальной длины из уже рассмотренных, в которой он будет последним элементом. Значит, записываем его в конец <tex>B</tex>. | ||
| + | * Иначе <tex>\pi_i</tex> будет наилучшим элементом для уже существующей длины, тогда мы находим наименьшее <tex>k\colon B_k > \pi_i</tex> и заменяем <tex>B_k</tex> элементом <tex>\pi_i</tex>. | ||
| − | + | Следует отметить, что полученный массив также образует возрастающую последовательность, на котором мы должны выполнять операции <tex>\mathrm{insert}, \mathrm{next}, \mathrm{delete}</tex>, соответственно целесообразно использовать [[ Приоритетные очереди | приоритетную очередь]], реализованную через [[Дерево ван Эмде Боаса]]. Так как данная структура данных производит описанные операции за <tex>O(\operatorname{log} k)</tex>, где k — количество бит чисел, которые позволяет хранить дерево, то полученный алгоритм работает за <tex>O(n\operatorname{log}\operatorname{log} n)</tex>, потому что все элементы последовательности не превосходят n. | |
| − | |||
| − | |||
| − | Следует отметить, что полученный массив также образует возрастающую последовательность, на котором мы должны выполнять операции <tex>\mathrm{insert}, \mathrm{next}, \mathrm{delete}</tex>, соответственно целесообразно использовать [[ Приоритетные очереди | приоритетную очередь]], реализованную через [[Дерево ван Эмде Боаса]]. Так как данная структура данных | ||
==== Пример ==== | ==== Пример ==== | ||
Версия 21:29, 30 декабря 2017
| Задача: |
| Дана перестановка множества. Требуется найти НВП за , где — длина НВП. |
Содержание
Алгоритм за O(n log log n)
Нахождение длины НВП
Основная идея
Пусть — входная перестановка.
Будем последовательно обрабатывать элементы в порядке
Для каждой длины предполагаемой НВП находим наименьший элемент, который может быть последним в возрастающей подпоследовательности длины и запишем его в массив . Будем называть его наилучшим элементом для длины .
- Если больше каждого элемента , вычисленного для подпоследовательности , значит с ним можно сделать возрастающую подпоследовательность максимальной длины из уже рассмотренных, в которой он будет последним элементом. Значит, записываем его в конец .
- Иначе будет наилучшим элементом для уже существующей длины, тогда мы находим наименьшее и заменяем элементом .
Следует отметить, что полученный массив также образует возрастающую последовательность, на котором мы должны выполнять операции , соответственно целесообразно использовать приоритетную очередь, реализованную через Дерево ван Эмде Боаса. Так как данная структура данных производит описанные операции за , где k — количество бит чисел, которые позволяет хранить дерево, то полученный алгоритм работает за , потому что все элементы последовательности не превосходят n.
Пример
Типы операций
- Добавление элемента, который больше всех предыдущих:
- Замещение элемента более подходящим, т.е. добавление немаксимального элемента:
Пример последовательности
| 9 | 3 | 10 | 4 | 8 | 1 | 2 | 12 | 6 | 5 | 7 | 11 |
Состояние очереди при каждом добавлении
| 9 | 9 | ||||
| 3 | 3 | ||||
| 3 | 10 | 10 | |||
| 3 | 4 | 4 | |||
| 3 | 4 | 8 | 8 | ||
| 1 | 4 | 8 | 1 | ||
| 1 | 2 | 8 | 2 | ||
| 1 | 2 | 8 | 12 | 12 | |
| 1 | 2 | 6 | 12 | 6 | |
| 1 | 2 | 5 | 12 | 5 | |
| 1 | 2 | 5 | 7 | 7 | |
| 1 | 2 | 5 | 7 | 11 | 11 |
Псевдокод
int LIS([n]) PriorityQueue B // рабочая приоритетная очередь int k = 0 // длина НВП for i = 1 to n x = [i] // в любом случае добавляем в очередь очередной элемент // устаревшие будем удалять B.insert(x) if B.next(x) // добавленный элемент — не максимальный // удаляем следующее за x значение B.delete(B.next(x)) else // добавленный элемент — максимальный // предыдущие значения не трогаем, очередь увеличилась k = k + 1 return k
Расширение алгоритма до нахождения НВП
Основная идея
Будем запоминать пары: для каждого элемента записываем его "предшественника".
Тогда, пройдя по предшественникам, начиная с последнего элемента очереди , мы можем восстановить НВП.
Общий вид алгоритма
| 9 | 9 | ||||
| 3 | 3 | ||||
| 3 | 10 | 10 | |||
| 3 | 4 | 4 | |||
| 3 | 4 | 8 | 8 | ||
| 1 | 4 | 8 | 1 | ||
| 1 | 2 | 8 | 2 | ||
| 1 | 2 | 8 | 12 | 12 | |
| 1 | 2 | 6 | 12 | 6 | |
| 1 | 2 | 5 | 12 | 5 | |
| 1 | 2 | 5 | 7 | 7 | |
| 1 | 2 | 5 | 7 | 11 | 11 |
| predecessor | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 1 | 3 | 2 | 2 | 5 | 4 | 3 | 7 | 8 | |||
Псевдокод
int[] LIS([n]) PriorityQueue B int k = 0 int predecessor[n] // резервируем позиций for i = 1 to n x = [i] B.insert(x) predecessor[x] = B.prev(x) if B.next(x) B.delete(B.next(x)) else k = k + 1 // по цепочке от последнего элемента // восстанавливаем НВП int result[k] int cur = B.max for i = k - 1 downto 0 result[i] = cur cur = predecessor[cur] return result
Оптимизация до O(n log log k)
Чтобы Дерево ван Эмде Боаса выполняло операции за , необходимо алфавит обрабатываемых значений уменьшить до .
Предположим, мы знаем такое приближение числа числом . Мы обсудим, как найти такое позже.
Чтобы достичь нужной оценки, будем делить последовательность на блоков, кроме последнего, который может быть меньше, и обрабатывать каждый блок отдельно.
Деление на блоки
Последовательность делится на блоки , последвовательных элементов:
Обозначим за отсортированный блок . Отсортированные и неотсортированные блоки будем хранить в памяти.
Цифровая сортировка каждых блоков отдельно будет давать нам время рваботы . Чтобы отсортировать их за линейное время, дополним каждый элемент номером его блока и получим пары . Цифровая сортировка этих пар, если принимать за старший разряд номер блока, а за младший значение элемента, будет работать , потому что значения элементов и номера блоков не превосходят .
Обработка блока
Обрабатывая блоки, будем работать не со значениями элементов, а с ключами, которые определенны для каждого элемента внутри блоков. Все блоки будут обрабатываться онлайн, то есть мы не перейдём к обработке следующего блока, пока не закончим с текущим.
Каждому элементу взаимно однозначно сопоставим ключ . Если все значения ключей будут находятся в промежутке , то эффективней будет работать с ключами элементов в очереди .
Чтобы определить ключи элементам так, чтобы их значения были в представленном промежутке, работая с блоком будем сливать элементы, ключи которых находятся в очереди с в список . Сопоставим каждому элементу в списке его позицию. Это и будет наш ключ. Заметим, что элементы, чьи ключи находятся в располагаются в возрастающеме порядке, поэтому достаточно производить тривиальную операцию слияния. Поскольку мы предположили, что , то количество ключей в не больше , тогда длина не больше , что позволяет однозначно определить ключи на множестве .
После того, как ключи определенны, обновляем ключи в очереди .
После этого запускаем, описанный выше алгоритм , для ключей элементов в порялке исходной последовательности.
В итоге, обработка блока делится на следующие этапы:
- Достаем из очереди ключи , конвертируем их в элементы и кладём в список .
- Сливаем элементы в со следующим отсортированным блоком в список .
- Присваеваем новые ключи элементам в порядке списка .
- Вставляем в новые ключи элементов .
- Обрабатываем ключи элементов блока в порядке исходной последовательности с помощью алгоритма . Для восстановления НВП также используем массив "предшественников", который будет работать с соответсвующими ключам элементами .
Пример
Предположим, что . Исходно получаем:
| Блок | 1 | 1 | 1 | 1 | 1 | 2 | 2 | 2 | 2 | 2 | 3 | 3 |
| 9 | 3 | 10 | 4 | 8 | 1 | 2 | 12 | 6 | 5 | 7 | 11 |
После сортировки:
| Блок | 1 | 1 | 1 | 1 | 1 | 2 | 2 | 2 | 2 | 2 | 3 | 3 |
| 3 | 4 | 8 | 9 | 10 | 1 | 2 | 5 | 6 | 12 | 7 | 11 |
Первый блок
|
| ||||||||||||||||||||||||||||||||||||||
Обработка блока с помощью алгоритма .
| 4 | 4 | 9 | ||
| 1 | 1 | 3 | ||
| 1 | 5 | 5 | 10 | |
| 1 | 2 | 2 | 4 | |
| 1 | 2 | 3 | 3 | 8 |
В результате получаем
Второй блок
Восстанавливаем элементы из : .
Сливаем и восстановеленные элементы из :
|
|
| ||||||||||||||||||||||||||||||||||||||||||||
|
| ||||||||||||||||||||||||||||||||||||||
Обновляем ключи в очереди:
| 3 | 3 | ||
| 3 | 4 | 4 | |
| 3 | 4 | 7 | 7 |
новых:
| 1 | 4 | 7 | 1 | 1 | |
| 1 | 2 | 7 | 2 | 2 | |
| 1 | 2 | 7 | 8 | 8 | 12 |
| 1 | 2 | 6 | 8 | 6 | 6 |
| 1 | 2 | 5 | 8 | 5 | 5 |
В результате получаем:
Третий блок
Восстанавливаем элементы из : .
Сливаем и восстановленные элементы из :
|
|
| |||||||||||||||||||||||||||||||||
Обновление старых ключей:
новых:
Результат завершения алгоритма:
Получаем, что длина НВП - 5, и НВП оканчивается на . Восстановление НВП
Начинаем восстановление с :
Оценка времени работыТак как размер списка не больше , а количество блоков всего . То количество присваиваний новых ключей элементам последовательности не больше , где c — некоторая константа. Каждая операция с приоритетной очередью требует времени, так как элементы в не больше . Докажем, что реализация данного алгоритма будет работать за время для последовательности длины n. Рассмотрим последовательность , где , — некоторое значение, меньшее . Будем по порядку для элементов этой последовательности запускать алгоритм, представленный выше. Если размер очереди становится больше , то условие перестает выполняться, тогда останавливаем алгоритм, и переходим к следующему элементу . Когда найдётся первое , то алгоритм успешно завершится. Таким образом, время работы алгоритма — для , потому что во время работы очередь хранит не более элементов, ключи которых не больше . Для значения алгоритм успешно завершается, так как условие полной обработки последовательности выполняется. Таким образом, время работы алгоритма для также . Заметим, что .
Общее время работы алгоритма — .
Тогда алгоритм работает за . См. также
Источники информации |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||



