Блог пользователя Math_Tereegor

Автор Math_Tereegor, история, 3 месяца назад, перевод, По-русски

Всем привет!

В спортивном программировании линейные рекурренты с постоянными коэффициентами — это стандартная тема. Такие задачи обычно решаются построением матрицы переходов и использованием бинарного возведения в степень для нахождения $$$N$$$-го члена за время $$$O(\log N)$$$ (или с помощью алгоритма Бостана-Мори для больших порядков).

Однако подход должен измениться, если коэффициенты не постоянны, а зависят от $$$n$$$.

Что такое голономная последовательность?

Последовательность $$$a_n$$$ называется голономной (или P-рекурсивной), если она удовлетворяет линейному рекуррентному соотношению, коэффициентами в котором выступают многочлены от $$$n$$$.

Формально это значит, что существуют такие многочлены $$$P_0(x), P_1(x), \dots, P_k(x)$$$ (причем $$$P_k \neq 0$$$), что для любого $$$n$$$ выполняется:

$$$ P_k(n) a_{n+k} + P_{k-1}(n) a_{n+k-1} + \dots + P_0(n) a_n = 0 $$$

Целое число $$$k$$$ — это порядок рекурренты, а максимальная степень среди многочленов $$$P_i$$$ — степень рекурренты.

Классические примеры:

  • Факториалы: $$$1 \cdot a_n - n \cdot a_{n-1} = 0$$$. (Порядок 1, Степень 1).
  • Числа Каталана: $$$(n+2) \cdot C_{n+1} - (4n+2) \cdot C_n = 0$$$. (Порядок 1, Степень 1).
  • Число беспорядков (Derangements): $$$1 \cdot D_n - (n-1) \cdot D_{n-1} - (n-1) \cdot D_{n-2} = 0$$$. (Порядок 2, Степень 1).

Применение в спортивном программировании

Ключевое свойство голономных последовательностей заключается в их замкнутости относительно различных операций. Если последовательности $$$A$$$ и $$$B$$$ голономны, то следующие последовательности также голономны:

  1. $$$A + B$$$
  2. $$$A \cdot B$$$
  3. Частичные суммы: $$$\sum_{i=0}^n A_i$$$

Это означает, что для сложных сумм, таких как $$$\sum_{k=0}^n \binom{n}{k}^2 \binom{n+k}{k}^2$$$ (числа Апери), математически гарантируется существование полиномиальной рекурренты, поскольку биномиальные коэффициенты и факториалы сами по себе голономны.

Аналитически эти рекурренты могут быть найдены с использованием алгоритма Цайльбергера (Creative Telescoping), который алгебраически упрощает суммирование.

Однако реализация алгоритма Цайльбергера во время контеста крайне непрактична. Так как теоретически гарантировано, что рекуррента существует, можно обойти аналитический вывод и вместо этого угадать рекурренту алгоритмически.

Автоматическое угадывание с помощью метода Гаусса

Рассмотрим сценарий, когда $$$N$$$-й член последовательности нужно найти за время $$$O(N)$$$ (для $$$N \le 10^7$$$), но доступный метод прямого подсчета или динамического программирования требует $$$O(N^2)$$$. Для автоматического вывода лежащей в основе рекурренты можно использовать линейную алгебру.

Предположим, что рекуррента имеет небольшой порядок $$$R$$$ (например, $$$R=2$$$) и полиномиальную степень $$$D$$$ (например, $$$D=1$$$). Уравнение принимает следующий вид:

$$$ (c_{2,1} n + c_{2,0}) a_n + (c_{1,1} n + c_{1,0}) a_{n-1} + (c_{0,1} n + c_{0,0}) a_{n-2} = 0 $$$

Поскольку значения $$$a_n, a_{n-1}, a_{n-2}$$$ и $$$n$$$ известны, единственными неизвестными являются константы $$$c_{i,j}$$$. Существует ровно $$$U = (R+1) \times (D+1)$$$ неизвестных констант. В данном примере $$$U = 3 \times 2 = 6$$$.

Реализация (C++)

Ниже представлено мое решение задачи 506E (Mr. Kitayuta's Gift) с использованием этого алгоритма.

Задача 506E: Дана строка $$$S$$$ ($$$|S| \le 200$$$). Вычислите количество валидных способов вставить ровно $$$N$$$ символов ($$$N \le 10^9$$$) так, чтобы итоговая строка стала палиндромом. Все вычисления должны производиться по модулю $$$10007$$$.

Как работает мое решение:

  1. Наивное ДП: Мы пишем простое $$$O(|S|^2 \cdot K)$$$ ДП для вычисления ответов на небольших длинах.
  2. Разделение по четности (Важно): Палиндромы четной и нечетной длины ведут себя как две независимые последовательности. Если их смешать, порядок рекурренты раздувается до $$$\sim 600$$$, что вызывает превышение лимита времени (TLE) при матричном умножении. Вычисляя элементы исключительно для целевой четности $$$(|S| + N) \pmod 2$$$, мы уменьшаем порядок рекурренты вдвое, до $$$\le 305$$$.
  3. Автоматическое угадывание: Мы генерируем $$$K = 800$$$ элементов и подаем их в алгоритм метода Гаусса с полиномиальной степенью $$$D=0$$$ (что по сути находит рекурренту с постоянными коэффициентами).
  4. Оптимизированное матричное умножение: Мы строим матрицу переходов и возводим её в степень $$$N/2$$$. Поскольку $$$MOD = 10007$$$, мы можем безопасно опустить операцию взятия по модулю в самом внутреннем цикле матричного умножения, позволяя решению уверенно отрабатывать за $$$\sim 0.3s$$$.

Code for 506E

Если есть какие-либо вопросы по этому методу или его реализации, пожалуйста, не стесняйтесь оставлять комментарии или писать в личные сообщения.

Полный текст и комментарии »

  • Проголосовать: нравится
  • +2
  • Проголосовать: не нравится