Динамическое программирование - теория.

1. Динамическое программирование

Довольно часто на олимпиадах встречаются задачи, провоцирующие к применению алгоритмы перебора. Но простой подсчет числа вариантов убеждает в неэффективности такого подхода. Для решения таких задач используется метод динамического программирования. Суть его заключается в том, что для отыскания решения поставленной задачи решается похожая (или похожие), но более простая. При этом осуществляется переход к еще более простым и так далее, пока не доходят до тривиальной.

Динамическое программирование обычно применяется к задачам, в которых искомый ответ состоит из частей, каждая из которых в свою очередь дает оптимальное решение некоторой подзадачи.

Динамическое программирование полезно, если на разных путях многократно встречаются одни и те же подзадачи; основной технический приём — запоминать решения встречающихся подзадач на случай, если та же подзадача встретится вновь.

В типичном случае динамическое программирование применяется к задачам оптимизации. У такой задачи может быть много возможных решений, но требуется выбрать оптимальное решение, при котором значение некоторого параметра будет минимальным или максимальным.

Из предыдущего рассуждения видно, что решение можно оформить рекурсивно. Но простое применение этого приема очень легко может привести к переполнению стека. Необходимо позаботиться об оптимизации рекурсивных проходов и не вычислять одно и то же значение несколько раз, сделать так называемое отсечение. Можно вообще отказаться от рекурсии и решать задачу "наоборот" — прежде "решить" тривиальные случаи, а затем переходить ко все более сложным. В авторских решениях подобных задач почти всегда встречается второй путь (он несколько быстрее), но в этом занятии рассмотрим оба — первый гораздо доступнее для понимания.

Типовой алгоритм решения задач методом динамического программирования:

Описать строение оптимальных решений.
Выписать рекуррентное соотношение, связывающие оптимальные значения параметра для подзадач.
Двигаясь снизу вверх, вычислить оптимальное значение параметра для подзадач.
Пользуясь полученной информацией, построить оптимальное решение.

Для решения задач оптимизации существует специальная теория, большая заслуга в ее создании принадлежит Р. Беллману. В общем виде она достаточна сложна, поэтому здесь не рассматривается. В то же время конкретные задачи, рассмотренные ниже, вполне могут сформировать (хотя бы на интуитивном уровне) идеи, лежащие в основе решения задач данного класса.

Первые две задачи, строго говоря, нельзя отнести к указанному классу, но приемы, использованные при их решении, очень сходны с таковыми у задач, рассматриваемых на этом занятии. Остальные задачи в свое время встречались на различных олимпиадах (а некоторые с тех пор стали "фольклорными") и расположены (по мнению автора публикации) в порядке возрастания сложности. Для простоты будем считать, что в большинстве задачах исходные данные таковы, что результат поместится в тип LongInt. Справедливости ради отметим, что такое ограничение существует не всегда, и в последних двух задачах приходится либо использовать тип Double, либо дополнительно реализовывать "длинную арифметику".