Участник:Artem.ustinov/НВП
Задача: |
Дана перестановка НВП за , где — длина НВП. | множества . Требуется найти
Алгоритм за 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.Рассмотрим последовательность , где , — некоторое значение, меньшее .Будем по порядку для элементов этой последовательности запускать алгоритм, представленный выше. Если размер очереди становится больше , то условие перестает выполняться, тогда останавливаем алгоритм, и переходим к следующему элементу . Когда найдётся первое , то алгоритм успешно завершится.Таким образом, время работы алгоритма — для , потому что во время работы очередь хранит не более элементов, ключи которых не больше . Для значения алгоритм успешно завершается, так как условие полной обработки последовательности выполняется. Таким образом, время работы алгоритма для также .Заметим, что .
Общее время работы алгоритма — .
Тогда алгоритм работает за .См. также
Источники информации |