материалы занятия

реклама
Методика и содержание подготовки учащихся к олимпиадам по программированию.
Дистанционный курс.
Динамическое программирование
Несколько слов о преподавании этой темы
Перед тем как приступить к изучению этой темы (или параллельно с ней) в качестве
существенного подспорья можно рассказать учащимся о методе математической индукции и
провести один-два семинара по решению математических задач.
Также, возможно кто-то найдет для себя полезным добавить к этому материалу тему
«индуктивные функции» (по Кушниренко А.Г.), некоторые задачи и теорию о которых можно
найти в книге «Программирование в теоремах и задачах».
Поскольку темы «Рекурсия», «Математическая индукция» и «Динамическое
программирование» опираются, по сути, на одну и ту же идею то имеет смысл изучать их без
больших перерывов по времени.
Пара слов о том, почему рекурсивное решение не всегда хорошо
При написании программ иногда может появиться проблема выбора — проводить ли
оптимизацию программы по времени работы или по используемой памяти. Хранить в памяти
(или во внешнем файле) некоторый информационный объект или тратить время на его
создание (например, расчет числа по формуле) каждый раз, когда в нем возникает
необходимость.
Рассмотрим задачу о нахождении k-го числа ряда: 1, 1, 2, 3, 5, 8, 13, 21... Каждый
элемент, кроме первых двух определяется как сумма двух предыдущих. В лекции о рекурсии
было записано решение этой задачи с помощью рекурсивной функции:
function F(k : integer): integer;
begin
if k<=2 then F:=1
else F:= F(k-1) + F(k-2);
end;
Недостаток данного решения будет очевиден, если изобразить, к примеру, такую схему:
F(8)
F(7)
F(6)
F(6)
F(5)
F(5)
F(4)
F(4)
F(5)
F(3)
F(4)
F(4)
F(3)
F(3)
F(2)
F(4)
Для того, чтобы найти F(8) нужно найти F(7) и F(6). Для F(7) нужно найти F(6) и F(5).
И так далее. Как видно из схемы, уже значение F(4) для разных целей должно при таком
решении быть вычислено 5 раз. Если речь пойдет о нахождении не F(8), а F(100), то
МЦНМО, 2007/08 учебный год
Методика и содержание подготовки учащихся к олимпиадам по программированию.
Дистанционный курс.
количество дублирующих друг друга вычислений будет еще больше.
Очевидно, что такое решение не является оптимальным с точки зрения времени работы
программы, несмотря на простоту рекурсивной реализации.
С другой стороны, рекурсивная идея решения (то есть возможность выразить любое
значение из этого ряда через предыдущие) заложена в самом определении ряда.
Чтобы выйти из такого «затруднения» пожертвуем памятью — будем хранить столько
предыдущих (уже вычисленных значений), сколько требуется для решения задачи и при этом
заменим рекурсивную функцию циклом.
a:=1;
b:=1;
for i:=3 to k do begin
F:=a + b;
a:=b;
b:=F;
end;
После исполнения такого цикла (естественно, предполагается что используемая система
программирования исполняет цикл от меньшего значения к большему ноль раз) F будет
равно искомому числу. При этом для вычисления каждого нового значения достаточно
хранить только два предыдущих.
По сути, в этом и состоит идея метода, который называется «динамическое
программирование». Также как и в при написании рекурсивного решения, нужны две четко
определенные вещи: правило по которому на основе предыдущих решений строится новое, и
условие, при котором решение очевидно. Отличие состоит в том, что решения задачи в более
простых случаях хранятся в памяти (в массивах, двумерных массивах или иным способом).
Пример. Дана полоска длины N клеток. В крайней левой клетке в начальный момент
времени находится фишка (см. рисунок). За один ход фишка может перемещаться вправо на
любое количество клеток от 1 до k. Определите количество способов переместить фишку в
крайнюю правую клетку полоски.
☻
На рисунке показан один из таких способов перемещения фишки при k=5.
Данная задача может быть переформулирована так: найти количество способов
представить число N в виде суммы, слагаемые в которой не превышают k, при этом суммы с
разным порядком слагаемых будут считаться разными.
При N=1 решение очевидно: существует ровно один способ оставить фишку на том же месте.
При N=2 решение также очевидно: переместить фишку в соседнюю клетку можно также
МЦНМО, 2007/08 учебный год
Методика и содержание подготовки учащихся к олимпиадам по программированию.
Дистанционный курс.
ровно одним способом1.
Пусть существуют правильные решения для всех полосок длины до L включительно и
они хранятся в массиве S[1..L] (на самом деле не всегда нужно хранить все предыдущие
решения, но пока «для страховки» предположим, что они хранятся). Требуется определить по
этим данным количество способов попасть в клетку с номером L+1 (на рисунке приведено
состояние массива при k=5 и L=7, фишка находится в той клетке, для которой необходимо
построить решение).
2 4 8 16 31 ☻
По условию задачи фишка перемещается на любое количество клеток от 1 до k. Это
значит, что в рассматриваемую клетку фишка может попасть только из тех, расстояние от
которых до нее не превышает k (на рисунке выделены цветом для k=5).
1
1
1 1 2 4 8 16 31 ☻
Рассмотрим путь из соседней слева клетки в текущую:
1
1
2
4
8 16 31 ☻
Очевидно, что существует ровно один способ попасть из клетки с номером L в клетку с
номером L+1. При этом у нас есть 31 (для нашего примера) путь, который оканчивается в
клетке с номером L. Это значит, что и для клетки с номером L+1 будет 31 путь, проходящий
через клетку с номером L.
Но в клетку с номером L+1 можно также попасть и из клетки с номером L-1:
1
1
2
4
8 16 31 ☻
Рассуждая по аналогии, к найденным прежде путям надо добавить 16, проходящих через
клетку с номером L-1.
Продолжим наши рассуждения для клетки с номером L-2:
1
1
2
4
8 16 31 ☻
Существует ровно один способ напрямую попасть из клетки с номером L-2 в клетку с
номером L+1 (обратите внимание, что нас интересует только прямой путь; пути, проходящие
через другие клетки уже посчитаны на предыдущих шагах). При этом в клетку с номером L-2
можно попасть 8 способами. Это значит, что всего есть 8 путей, ведущих напрямую из клетки
с номером L-2 в клетку с номером L+1. Эти 8 путей необходимо прибавить к уже найденным.
Продолжая рассуждения по аналогии мы получим правило: количество путей, ведущих
в клетку с номером L+1 равно сумме решений для предыдущих k клеток (разумеется, если
они существуют). То есть, заполнив массив S[] по формуле: S[L+1]:=S[L] + S[L-1]+...+S[L-k],
в элементе номер N мы получим ответ к задаче.
Программа для решения этой задаче может выглядеть, например, так:
const
N = 20;
{длина полоски}
1 Здесь можно обратить внимание на аналогию с базой индукции и с терминальным условием рекурсии.
МЦНМО, 2007/08 учебный год
Методика и содержание подготовки учащихся к олимпиадам по программированию.
Дистанционный курс.
var
S : array [1..N] of longint;
k, L, i : integer;
begin
read (k);
S[1]:=1;
{ ответы для первых двух клеток очевидны}
S[2]:=1;
for L:=3 to N do begin
S[L]:=0;
i:=L-1;
while (i>0)and(i>=L-k) do
begin
S[L]:=S[L] + S[i];
{для всех последующих клеток вычисляем}
dec(i);
{ответ как сумму k предыдущих }
end;
end;
Write(S[N]);
end.
Пример. Задача о «хромом короле». Дана доска, A x B клеток. В правом верхнем углу
ее находится «хромой» шахматный король — в отличие от обычного короля он может ходить
только на одну клетку вниз или на одну клетку вправо. Определить количество разных путей,
по которым король может попасть в правую нижнюю клетку.
На рисунке изображено несколько вариантов такого пути «хромого короля». Разные
продолжения пути показаны разными цветами.
K
Задача является практически двухмерным случаем предыдущей. Для начала заметим, что в
МЦНМО, 2007/08 учебный год
Методика и содержание подготовки учащихся к олимпиадам по программированию.
Дистанционный курс.
любую из клеток верхней строки и левого столбца король может попасть только одним
способом (база индукции, терминальное условие рекурсии).
K 1 1 1 1
1
1
1
1
1
1
Теперь построим динамический переход (сформулируем шаг индукции, правило
сведения задачи к более простой). Допустим, что у нас есть готовые правильные решения для
некоторого прямоугольного участка доски, который начинается в ее левом верхнем углу (на
рисунке показан такой участок размерами 4 x 3). Вернее для всего участка, за исключением
его правой нижней клетки:
K 1 1 1 1
1 2 3
1 3 6
1 4 ?
1
1
1
В клетку, отмеченную знаком вопроса можно попасть либо из соседней сверху, либо из
соседней слева. Причем, и из той, и из другой — ровно одним способом. Для данного
рисунка это означает, что существует 6 путей через верхнюю клетку и 4 — через левую.
Всего 10 (по аналогии с предыдущей задачей).
Пусть промежуточные решения хранятся в массиве S[1..n, 1..m]. Тогда S[i,j] определяется по
формуле S[i,j]:=S[i-1, j] + S[i, j-1] (при i<>1, j<>1).
Далеко не всегда параметры, от которых зависит решение, являются очевидными. Не
всегда первая попавшаяся самая очевидная формула перехода будет самой оптимальной.
Иногда их нужно поискать, попробовать решение задачи с разных сторон. Именно этим
метод динамического программирования отличается от многих других стандартных
методов и алгоритмов.
МЦНМО, 2007/08 учебный год
Методика и содержание подготовки учащихся к олимпиадам по программированию.
Дистанционный курс.
Пример2. Определите количество способов представить число N в виде суммы
положительных слагаемых, каждое из которых строго меньше предыдущего. Отдельное
число можно тоже считается «суммой» из одного единственного слагаемого. Общее
количество слагаемых в сумме не ограничено.
Например, число 6 можно представить такими суммами: 6; 5+1; 4+2; 3+2+1.
Применим метод динамического программирования. Для небольших чисел, таких как 1 или 2
— решение очевидно. Для N=1 ответ равен единице, для N=2 ответ также равен единице3.
Построим динамический переход. Пусть существуют правильные решения для всех чисел от
1 до N-1 и требуется на их основе сделать решение для числа N (на рисунке под решением
для конкретного N приведен комментарий как оно получено).
N=1
N=2
N=3
N=4
N=5
N=6
1
1
2
2
3
4
1
2
3
2+1
4
3+1
5
4+1
3+2
?
6
5+1
4+2
3+2+1
Рассмотрим пример. Число 7 (для которого строится решение) можно представить, к
примеру, как 7=6+1. Однако, попытка решить задачу аналогично первому примеру (о фишке)
будет неудачной. На первый взгляд казалось бы: есть 4 способа получить число 6, и ровно
один способ прибавить к 6 единицу. Однако, по условию задачи все слагаемые должны быть
различны. Это значит, что необходимо взять не все способы разложить число 6, а только те,
где еще нет числа 1. А таких способов не 4, а 2, но это никак не отражено в нашей
одномерной таблице.
Аналогично, если представить 7 как 5+2, то нужно учитывать не все способы
разложения числа 5, а только те, где не было числа 2.
Таким образом мы приходим к выводу, что помимо количества способов представить то
или иное число в виде суммы в данной задаче необходимо хранить сведения о том, каким
образом (из каких слагаемых) эта сумма получена.
Проведем некоторые рассуждения в общем виде. Пусть N можно представить как
сумму N= a + b. Пусть, в свою очередь, число b можно представить несколькими способами.
И требуется выбрать те из этих способов, в которых гарантировано нет числа a. Для этого
введем очень простое требование: будем учитывать только те способы разложения числа b, в
которых участвуют только слагаемые строго меньше чем а. В этом случае количество
таких сумм будет равно количеству разложений числа b со слагаемыми не более a. И для
решения данной задачи будет необходимо хранить два параметра — само число и
максимальное слагаемое, для которого построено промежуточное решение.
2 Для данной темы, впрочем как и для рекурсии, может понадобиться разбор большого количества примеров.
3 В задаче не конкретизируется, считается ли сумма из одного слагаемого «нормальной». Для определенности
в данном примере будем считать, что сумма из одного слагаемого имеет право на существование. Если
отбросить такие суммы, то суть решения практически не изменится.
МЦНМО, 2007/08 учебный год
Методика и содержание подготовки учащихся к олимпиадам по программированию.
Дистанционный курс.
Пусть массив S [1..n, 1..n] хранит решения. Тогда S[a,b] — количество способов
разложить число a в сумму со слагаемыми не более b.
В этом случае формула динамического перехода будет выглядеть так:
S[a, b]:=S[a, b-1] + S[a-b, b-1]
Первая часть формулы S[a, b-1] просто добавляет к текущему решению все способы
разложить число a с меньшими слагаемыми (не более b-1). Вторая часть S[a-b, b-1] взята из
предыдущих рассуждений и представляет собой количество способов записать число a-b с
максимальным слагаемым до b-1.
Принцип хранения данных в этой задаче показан на рисунке.
Максимальное
слагаемое
1 2 3 4 5 6 7 8 9 10
1 1
2 0 1
3 0 1 2
N
4 0 0 1 2
5 0 0 1 2 3
Количество способов
представить данное
число N (на примере 6)
в виде сумм, в которых
максимальное
слагаемое не
превышает данное (на
примере 4).
6 0 0 1 2 3 4
7 0 0 0 2 3 4 5
8 0 0 0 1 3 4 5 6
9 0 0 0 1 3 5 6 7 8
10 0 0 0 1 3 5 7 8 9 10
В первом столбце запишем все числа от 1 до того, до данного нам N. В первой строке
— различные слагаемые, с которых могут начинаться суммы.
На пересечении строки и столбца указывается число способов представить данное
число N (номер строки) в виде суммы слагаемых, из которых наибольшее не превосходит
данное (номер столбца). В примере, в 6-й строке 4-м столбце находится число 2. Это
значит, что если ограничить максимальное слагаемое числом 4, то у нас будет всего 2
способа записать сумму: 1+2+3 и 2+4. Остальные способы требуют больших слагаемых,
поэтому будут учтены позже.
Заполнив весь двухмерный массив по формуле S[a, b]:=S[a, b-1] + S[a-b, b-1] в ячейке с
координатами (n,n) мы получим искомое количество способов.
Еще более сложными задачами на метод динамического программирования являются
МЦНМО, 2007/08 учебный год
Методика и содержание подготовки учащихся к олимпиадам по программированию.
Дистанционный курс.
такие, где в результате решения получается не готовый ответ, а какая-то величина (или
массив) по которой необходимо собрать ответ с помощью дополнительных вычислений
(например, перебрав в определенном порядке элементы массива). Такие задачи относятся
уже к достаточно высокому олимпиадному уровню, и подготовкой такого уровня уже
занимаются не столько в рамках школы, сколько в рамках сборов разного уровня. Посему
задачи такой сложности в этом курсе рассмотрены не будут.
Задачи
1. Дан массив из N элементов, каждый из которых является либо нулем, либо единицей.
Определить количество групп единиц, разделенных нулями.
2. Определите, сколько существует способов расположить в массиве из N элементов
нули и единицы так, чтобы не было двух подряд идущих единиц.
МЦНМО, 2007/08 учебный год
Скачать