75 lines
6.3 KiB
Markdown
75 lines
6.3 KiB
Markdown
# Задача 05 — длиннейшая возрастающая подпоследовательность (C++)
|
||
|
||
## Глава I. Общая информация
|
||
|
||
- **Цель:** посчитать длину строго возрастающей подпоследовательности массива за
|
||
O(n log n), а не за наивные O(n²).
|
||
- **Почему это в Eltex:** задача — эталон на «знаешь ли ты приём tails + бинарный поиск
|
||
поверх DP», а сам паттерн «поддерживать отсортированный массив минимально возможных хвостов
|
||
и подставлять новый элемент через `lower_bound`» переиспользуется в задачах на планирование
|
||
и в сравнении версий (diff-подобные алгоритмы ищут именно длиннейшие совпадающие/растущие
|
||
подпоследовательности).
|
||
- **Что сдаётся:** `solution.cpp`, проходящий `python3 grade.py 05`.
|
||
|
||
## Глава II. Что нужно знать до старта
|
||
|
||
Наивное решение — DP: `dp[i]` = длина LIS, заканчивающейся на элементе `i`, пересчитывается
|
||
перебором всех `j < i` — O(n²), при 100 000 элементах это ~10¹⁰ операций и тест не пройдёт
|
||
(ограничение — 2 секунды).
|
||
|
||
Быстрое решение — «хвосты» (patience sorting): массив `tails`, где `tails[k]` — минимально
|
||
возможный последний элемент возрастающей подпоследовательности длины `k+1`, накопленной по
|
||
уже просмотренному префиксу. Массив `tails` всегда отсортирован по построению, поэтому для
|
||
каждого нового `x` бинарным поиском (`lower_bound`) находится первая позиция с
|
||
`tails[pos] >= x`: если такая позиция есть — `x` заменяет значение в ней (подпоследовательность
|
||
той же длины, но с меньшим хвостом — выгоднее для будущих продолжений); если позиции нет —
|
||
`x` дописывается в конец, увеличивая длину LIS на 1. Именно `lower_bound`, а не `upper_bound` —
|
||
строгое возрастание требует заменить первый элемент `>= x`, а не `> x` (иначе повторяющиеся
|
||
значения ошибочно продлевают подпоследовательность).
|
||
|
||
Итоговая длина `tails` в конце обработки массива и есть `lis_length`. Значения в `tails` —
|
||
это не сама LIS, только корректная её длина.
|
||
|
||
Почему дальше: тот же приём «поддерживай отсортированный инвариант + `lower_bound`» стоит
|
||
знать и для задач на минимальное число возрастающих подпоследовательностей на весь массив.
|
||
|
||
## Глава III. Задание
|
||
|
||
```c++
|
||
int lis_length(const std::vector<int>& a); // длина НВП (строго возрастающей)
|
||
```
|
||
|
||
Требования:
|
||
- пустая последовательность → 0;
|
||
- работа за O(n log n); наивное O(n²) не пройдёт проверку на 100 000 элементах
|
||
(внутренний тест меряет время и роняет прогон при > 2 с);
|
||
- подпоследовательность не обязана быть непрерывной.
|
||
|
||
Проверка: `python3 grade.py 05`. Критерий: все `ok`, сборка без предупреждений.
|
||
|
||
## Глава IV. Ловушки
|
||
|
||
- `upper_bound` вместо `lower_bound` при строгом возрастании → повторяющиеся значения
|
||
ошибочно продлевают подпоследовательность → длина LIS завышена на массивах с дубликатами
|
||
(например, `[1, 1, 1]` должен дать 1, а не 3).
|
||
- Наивная DP-версия за O(n²) → проходит маленькие тесты, но на 100 000 элементах превышает
|
||
лимит в 2 секунды → `grade.py 05` роняет прогон по таймауту, а не по неверному ответу.
|
||
- Не обработан пустой вектор отдельным веткой → если код полагается на то, что цикл по
|
||
пустому диапазону сам вернёт 0, это обычно верно, но стоит явно проверить — тихая логическая
|
||
ошибка здесь не кидает исключение, просто даёт неверный ответ на пограничном тесте.
|
||
|
||
**Факты для карточек**
|
||
- base | Сложность наивного DP-решения LIS? — O(n²)
|
||
- core | Сложность решения через `tails` + бинарный поиск? — O(n log n)
|
||
- core | Что хранит `tails[k]`? — минимально возможный последний элемент возрастающей
|
||
подпоследовательности длины `k+1`
|
||
- deep | Почему для строгого возрастания нужен `lower_bound`, а не `upper_bound`? —
|
||
`lower_bound` находит первый элемент `>= x` и заменяет его, не давая повторам продлевать
|
||
подпоследовательность
|
||
|
||
## После сдачи
|
||
|
||
Разбор: почему массив `tails` не является самой LIS, а только хранит её длину через
|
||
минимальные возможные хвосты; как по `tails` при необходимости восстановить саму
|
||
подпоследовательность (дополнительный массив предшественников).
|