Files

75 lines
6.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Задача 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` при необходимости восстановить саму
подпоследовательность (дополнительный массив предшественников).