Соотношение переписывается в код построчно: каждая его часть имеет прямое соответствие в программе.
| Строка соотношения |
Что задаёт |
Строка кода |
| $F(1) = 1$ |
база |
F[1] = 1 |
| $F(n) = n \cdot F(n-1)$ |
шаг |
F[n] = n * F[n - 1] |
| порядок вычисления |
от базы по возрастанию $n$, каждое значение вычисляется один раз |
for n in range(2, N + 1) |
| ответ |
значение выражения, а не самой функции |
print((F[3038] + 5 * F[3037]) // F[3036]) |
Почему цикл, а не рекурсия. Соотношение допускает и запись рекурсивной функцией: на задании 13 применялся именно такой подход, но там глубина составляла несколько десятков. Здесь глубина равна 3038, а Python по умолчанию обрывает вложенность на 1000 вызовах и завершает выполнение с RecursionError. Предел повышается вызовом sys.setrecursionlimit(30000), и рекурсия без декоратора после этого отрабатывает: начиная с Python 3.11 вызов одной Python-функции из другой не занимает системный стек.
Второй приём, применённый в задании 13, – декоратор @lru_cache – здесь неприменим. Он реализован на C, поэтому каждый вызов через него занимает кадр в стеке потока; стек исчерпывается на глубине порядка тысячи вызовов (точное значение зависит от системы), и повышение предела рекурсии на это не влияет. Кеширование здесь и не требуется: цепочка $F(3038) \to F(3037) \to \dots$ линейная, каждое значение вычисляется ровно один раз. Цикл устраняет все три сложности – предел вложенности, стек и декоратор.
Почему //, а не /. / всегда даёт float, а во float помещаются числа примерно до 1e308 – 3038! с его 9263 знаками в этот диапазон не попадает. При делении факториала на факториал Python справляется: он делит длинные целые точно и округляет уже частное. Однако если в выражении есть деление на константу (например, выражения вида $F(n) / 519$), выполнение прерывается ошибкой OverflowError: integer division result too large for a float. Целочисленное деление работает с целыми числами и такой ошибки не даёт.