Теория
Что нужно знать
Задание 16 проверяет рекурсию: значение функции F(n), количество вызовов, сумму или количество выведенных символов, иногда — неизвестный параметр. В базе Kompege по теме 1 011 задач, 70 решений (61 программное и 9 текстовых) и 260 видеоразборов (161 YouTube, 66 VK, 33 Rutube). В 2027 году тема сохраняется без изменения номера.
Рекурсивная функция и её части
Рекурсивная функция вызывает саму себя с другим аргументом. В задании 16 всегда есть базовый случай, который не делает нового вызова, и рекурсивный шаг. Без базы вычисление не завершится, а неверная база даст неверный ответ.
- Базовый случай возвращает значение сразу: обычно при n ≤ b или n ≥ N.
- Рекурсивный шаг приближает аргумент к базе: n−1, n−2, n//2 или n+1 при базе сверху.
- Условия в записи читаются сверху вниз; срабатывает первая подходящая ветвь.
- Ветвь может зависеть от n, от n % 2, от n % 3 или от значения F(n−1).
- Аргументов может быть несколько: F(a, b), F(n, s), F(n, flag).
F(n) = база, если условиеF(n) = выражение с F(...), иначе
Дерево вызовов
Каждый вызов — узел дерева. Листья — базовые случаи. Значение считается снизу вверх: сначала листья, затем родители. Дерево помогает не потерять ветвь и посчитать количество вызовов.
- Корень — исходный вызов F(n).
- Дети — вызовы, которые делает текущий вызов.
- Лист — вызов, попавший в базовый случай.
- Количество вызовов — число узлов дерева, включая корень и листья.
- Глубина дерева — максимальная длина цепочки вызовов.
- Для больших n дерево не рисуют полностью, а сворачивают в таблицу или формулу.
вызовов = 1 + Σ вызовов в детяхзначение = шаг(значения детей)
Линейная рекурсия
Если в правой части один вызов, вычисление идёт цепочкой. Если два вызова, появляется ветвление. Цепочку раскрывают подстановкой, пока не дойдут до базы, а затем считают снизу вверх.
- F(n) = n + F(n−1) даёт сумму n + (n−1) + … + F(b).
- F(n) = n·F(n−2) даёт произведение чисел той же чётности.
- При базе n ≤ b подстановку останавливают на первом n из базы.
- Шаг n−2 сохраняет чётность: чётные и нечётные цепочки не пересекаются.
- Для произведения следите за нулём: F(0) = 0 обнулит всю цепочку.
F(n) = n + (n−1) + … + (b+1) + F(b)F(n) = n·(n−2)·…·(b+2)·F(b)
Рекурсия вперёд
Если база задана при n ≥ N, а шаг увеличивает аргумент, вычисляют в обратном порядке: от границы базы вниз к нужному n. Таблицу заполняют с конца.
- Определите границу базы: n ≥ N, n > N или n = N.
- Начинайте с наибольшего n и двигайтесь вниз.
- При шаге 2n значения нужны только для степеней двойки.
- Если аргумент уходит за границу, срабатывает база.
- Для F(5) при шаге 2n цепочка: 5 → 10 → 20 → … до базы.
F(N) = база; F(N−1), F(N−2), … — обратный ходF(2n) требует значения только в точках 2^k·n
Ветвление по условиям
В задании 16 ветвь часто выбирается по чётности n, остатку от деления или значению предыдущего вызова. Сначала вычислите условие, затем выбирайте формулу. Порядок условий важен: ветви могут пересекаться.
- n % 2 == 0 — чётное; n % 2 == 1 — нечётное.
- n % 3 == 0 — делится на 3; проверяйте остатки 0, 1 и 2.
- Условие может смотреть на F(n−1): «если F(n−1) чётно».
- Для отрицательных n в Python остаток неотрицательный: −3 % 10 = 7.
- Если условие не выполнено ни для одной ветви, функция не определена — проверьте базу.
ветвь 1, если условие 1ветвь 2, если условие 2ветвь 3, иначе
Таблица и динамика
Вместо рекурсии значения хранят в массиве или словаре и заполняют по порядку. Это снимает повторные вычисления и риск переполнения стека. Для рекурсии вперёд порядок заполнения обратный.
- Заведите массив на диапазон от базы до нужного n.
- Сначала заполните базовые значения.
- Затем применяйте формулу для n по возрастанию или по убыванию.
- Для F(n−2) нужны два предыдущих значения, для F(n−5) — пять.
- Для двух функций храните два массива или пары значений.
- Проверка: f[n] должно совпадать с рекурсивным вызовом на малых n.
f[n] = выражение(f[n−1], f[n−2], …)f[n] = f[n−1] + f[n−2] — числа Фибоначчи
Мемоизация
Мемоизация кэширует результат для каждого аргумента. Один и тот же F(k) не вычисляется повторно, поэтому дерево превращается в граф зависимостей и работает за число различных аргументов.
- from functools import lru_cache; @lru_cache(None) ставится перед def.
- В Python 3.9+ доступен @cache как короткий вариант.
- Кэш работает только для хешируемых аргументов: int, tuple, str.
- Для больших n прогрейте кэш циклом или увеличьте глубину рекурсии.
- Мемоизация не меняет значение функции, только скорость.
- Не используйте кэш, если функция зависит от внешней изменяемой переменной.
- @lru_cache(maxsize=N) ограничивает кэш N значениями; при переполнении старые вытесняются.
- Если в задаче задан размер кэша, эмулируйте его вручную: dict и очередь вытеснения, иначе количество повторных вычислений будет неверным.
- Для точного количества вызовов при ограниченном кэше декоратор может дать неверный ответ: часть вызовов пересчитается.
@lru_cache(None)@lru_cache(maxsize=100)from functools import cache
Количество вызовов
Количество вызовов — это не значение функции, а размер дерева. Для одного вызова это 1 плюс число вызовов детей. Если считают только рекурсивные вызовы, корень может не учитываться — уточняйте формулировку.
- Если считают все обращения к функции, каждый узел даёт 1.
- Для F(n) = F(n−1) + F(n−2) число вызовов растёт как числа Фибоначчи.
- Мемоизация уменьшает число вычислений, но не число обращений в наивной программе.
- Для процедуры с print считайте каждый вход в процедуру.
- Формулу удобно проверить на малых n вручную.
- Ответ может быть очень большим; в Python он не переполняется.
C(n) = 1, если базаC(n) = 1 + C(n−1) + C(n−2)
Что выводит процедура
В задачах со звёздочками и числами важен порядок команд: print до рекурсивного вызова выполняется при каждом входе, print после — при возврате. Суммируйте вклад текущего вызова и детей.
- Один print внутри процедуры — одна единица вклада на каждый вызов.
- print после рекурсивного вызова тоже выполняется, но позже.
- Если print стоит под if, он добавляется только при истинном условии.
- Для звёздочек: S(n) = вклад текущего вызова + S(детей).
- Для чисел складывайте напечатанные значения, а не аргументы.
- Порядок вывода влияет на последовательность, но не на сумму и количество.
S(n) = print_сейчас + S(n−1) + S(n−2)количество = сумма вкладов всех вызовов
Суммы и большие n
Если нужна сумма F(k) на отрезке или значение при большом n, рекурсию сворачивают в формулу. Часто помогают арифметическая прогрессия, суммы квадратов и периодичность.
- Сумма арифметической прогрессии: (a₁ + aₖ)·k / 2.
- Сумма первых m нечётных чисел равна m².
- Сумму значений F на отрезке считают циклом по таблице.
- Для больших n ищите шаг и повторяющийся блок.
- В Python большие целые точны; избегайте float для точных ответов.
- Если ответ просят по модулю, берите остаток на каждом шаге.
S = (a₁ + aₖ)·k / 21 + 3 + … + (2m−1) = m²
Несколько функций
Взаимная рекурсия: F вызывает G, G вызывает F. Значения вычисляют парами, а таблицу заполняют одновременно для обеих функций.
- База задаётся для каждой функции отдельно.
- Храните пару (F(n), G(n)) или два массива.
- При шаге назад начинайте с базы и идите вверх.
- При шаге вперёд идите от границы вниз.
- В Python кортежное присваивание F, G = …, … вычисляет правую часть до записи.
(F(n), G(n)) = Φ(F(n−1), G(n−1))F(n) = G(n−1), G(n) = G(n−2) + 1
Количество n по условию
Иногда нужно не одно значение, а число аргументов, для которых F(n) обладает свойством: является квадратом, содержит заданную цифру или кратно числу. Тогда F(n) считают для каждого n из диапазона и проверяют свойство.
- Определите границы диапазона: включительно или нет.
- Заполните таблицу F(n) до верхней границы.
- Свойство проверяйте после вычисления значения.
- Полный квадрат: x ≥ 0 и int(x**0.5)**2 == x.
- Наличие цифры: str(x).count('7') > 0.
- Кратность: x % m == 0.
- Разные n могут давать одно и то же F(n); считают именно n.
количество = |{n ∈ [a; b] : P(F(n))}|
Обратная задача и проверка
Иногда требуется найти параметр x или границу, при которых выполняется условие. Сначала упростите выражение, затем переберите x в разумных пределах и проверьте ответ.
- Выразите разность F(a) − F(b) через несколько шагов рекурсии.
- Для линейного x получается уравнение A + B·x = C.
- Диапазон перебора берите из условия; при отсутствии — от −10⁵ до 10⁵.
- Проверьте, что x целый и входит в допустимую область.
- Убедитесь, что рекурсия завершается и не уходит в бесконечность.
- Сверьте ответ с ключом на малых аналогах задачи.
F(a) − F(b) = A + B·xx = (C − A) / B