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

У нас есть какая-то задача и мы не знаем как её решить сразу, за один шаг, делим её на какие-то подзадачи меньшего размера и потом собирая решения из подзадач получаем решение общей задачи.

Т.е. динамическое программирование - метод решения задач путём разделения задачи на подзадачи с которыми мы можем справиться.

Задача: вычислить $N$-ое число Фибоначчи

Мы знаем рекуррентное отношение $F(n) = F(n-1) + F(n-2)$

Общая схема: База -> Индуктивный переход

    

Идея метода динамического программирования состоит в сведении (с помощью рекуррентных соотношений) исходной задачи к решению некоторых ее подзадач «меньшего размера» и использовании табличной техники для сохранения уже найденных ответов [2]. Рассказ о методе динамического программирования уместно начинать с легенды о лестнице фараона [12]. Золотую лестницу фараона из девяти ступеней необходимо было модернизировать, уменьшив количество ступеней в лестнице до четырех и используя небольшое количество золота, имеющееся в казне для наращивания ступеней. Золота было так мало, а всех вариантов модернизации так много, что не сносить бы несчастному казначею головы, если бы не его умный друг-жрец.  Жрец, прекрасно владея чудесным методом динамического программирования, сумел за короткий срок, не рассматривая всех вариантов, найти оптимальное решение и выгадать себе остаток золота из казны фараона.
Разбор задач по данному методу легче воспринимается с самой известной и простой задачи про маршрут [13].

Задача 1. В таблице A размерности $NxN$ клетки заполнены случайным образом цифрами от 0 до 9. Найти маршрут из левой верхней клетки A(1, 1) в правую нижнюю клетку A(N, N) такой, что

Вывести маршрут как последовательность пар координат клеток, через которые он проходит (первая координата – номер строки, вторая – номер столбца).

Очевидно, что все кратчайшие маршруты идут только сверху вниз и слева направо и длина их 2N-1. При достаточно большом N таких маршрутов огромное количество, что полным перебором решить данную задачу практически невозможно.

Итак, рассмотрим аналогичную задачу для всевозможных прямоугольных подтаблиц размеров (N-I+1)x(N-J+1) исходной таблицы (при I, J = 1, 2, …) и построим вспомогательную таблицу B размерности NxN с суммой цифр, через которые проходит оптимальный путь в такой таблице.
Построение вспомогательной таблицы B необходимо начать с конца таблицы A, что приведет к следующим формулам (рис. 1):

Картинка
  1. B(N, N) = A (N, N)
    последняя клетка обязательно входит в маршрут
  2. B(N, J) = B(N, J+1) + A (N, J) при J = N-1, N-2, … , 1
    если маршрут вышел в последнюю строку таблицы, то он обязательно будет проходить до конца по последней строке, так как он кратчайший
  3. B(I, N) = B(I+1, N) + A (I, N) при I = N-1, N-2, … , 1
    если маршрут вышел в последний столбец таблицы, то он обязательно будет проходить до конца по последнему столбцу
  4. B(I, J) = max (B(I+1, J),  B(I, J+1)) + A (I, J) при I<N и J<N
    внутри таблицы в маршрут будет входить та клетка из последующего столбца или последующей строки, в которой сумма цифр больше

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

Программа .1:
RANDOMIZE TIMER
CLS
INPUT "Введите размерность N: "; n
DIM a(n, n), b(n, n)
FOR i = 1 TO n
FOR j = 1 TO n
a(i, j) = INT(RND * 10)
PRINT a(i, j);
NEXT j
PRINT
NEXT i
'Формирование дополнительной матрицы
b(n, n) = a(n, n)
FOR i = n - 1 TO 1 STEP -1
b(i, n) = b(i + 1, n) + a(i, n)
b(n, i) = b(n, i + 1) + a(n, i)
NEXT i
FOR i = n - 1 TO 1 STEP -1
FOR j = n - 1 TO 1 STEP -1
b(i, j) = max(b(i + 1, j), b(i, j + 1)) + a(i, j)
NEXT j, i
PRINT
PRINT "Сумма чисел на всем кратчайшем пути = "; b(1, 1)
'Путь ищется обратным проходом
PRINT "( 1; 1)";
i = 1: j = 1
WHILE (i <> n) OR (j <> n)
IF i = n THEN                                    'Если дошли до нижней границы
j = j + 1
ELSE
IF j = n THEN                       'Если дошли до правой границы
i = i + 1
ELSE
IF b(i + 1, j) > b(i, j + 1) THEN i = i + 1 ELSE j = j + 1
END IF
END IF
PRINT USING " - (##;##)"; i; j;
WEND

FUNCTION max (a, b)
IF a > b THEN max = a ELSE max = b
END FUNCTION

Задача 2. «Акирема». (Областная олимпиада 1998).
В государстве Акирема алфавит состоит из букв американского языка. Каждый акиремец (житель) имеет право пополнить словарь своего языка новым словом.
Слово формируется по следующим правилам:

Какое новое слово может  сформировать акиремец по предложенным словам.

Примечание. Данные вводятся из файла с именем INPUT.TXT. В файле они заданы так: числа N и M – на первой строке через разделитель, каждое слово задаётся с новой строки.
Вывод осуществляется на экран или в файл с именем OUTPUT.TXT.

Пример: из слов
A P P L E
W I N D O W S
M O U S E
B Y T E

должно получиться слово PWOY.

Указание. Задача легко сводится к предыдущей. Строится матрица A размера NxM, где N – количество слов, а M – количество букв в самом длинном слове (табл. 1). Массив первоначально заполняется кодами ASCII букв каждого слова, расположенного в матрице по строкам с выравниванием по левой стороне (для коротких слов в матрице на местах отсутствия букв проставляются нули).

Таблица 1


65

80

80

76

69

0

0

87

73

78

68

79

87

83

77

79

85

83

69

0

0

66

89

84

69

0

0

0

Затем массив преобразуется по следующему правилу (табл. 2):

Таблица 2


320

335

332

328

315

0

0

255

247

252

242

246

225

83

166

168

174

167

138

0

0

66

89

84

69

0

0

0

В первой строке матрицы ищется наибольший элемент – это и есть искомая максимальная сумма кодов ASCII. Обратным ходом, выбирая всегда наибольшее значение, необходимо двигаться сверху вниз (с первой строки до последней), составляя строку из соответствующих букв. Искомое слово найдено - PWOY.

Программа 2:
DEFINT A-Z
OPEN "input.txt" FOR INPUT AS #1
INPUT #1, n, m
DIM Word$(1 TO n), Marks(0 TO n, 0 TO m + 1)

FOR i = 1 TO n
INPUT #1, Word$(i)
FOR j = 1 TO LEN(Word$(i))
Marks(i, j) = ASC(MID$(Word$(i), j, 1)) + max(Marks(i - 1, j - 1), _
Marks(i - 1, j), Marks(i - 1, j + 1))
NEXT
NEXT

k = 1
FOR i = 2 TO LEN(Word$(n))
IF Marks(n, i) > Marks(n, k) THEN k = i
NEXT

FOR i = n TO 1 STEP -1
s$ = MID$(Word$(i), k, 1) + s$
IF Marks(i - 1, k - 1) > Marks(i - 1, k + 1) THEN k = k - 1
IF Marks(i - 1, k + 1) > Marks(i - 1, k) THEN k = k + 1
NEXT
PRINT s$

FUNCTION max (a, b, c)
IF a > b THEN
IF a > c THEN max = a ELSE max = c
ELSE
IF b > c THEN max = b ELSE max = c
END IF
END FUNCTION

Задача 3. «INTERNETомания». (Районная олимпиада 1999)

1Компания «Питерские сети» проводит подключение к сети INTERNET N-этажных домов. На каждом этаже компьютеры подключаются к концентратору (HUB) по топологии «звезда», т.е. каждый компьютер соединяется с концентратором отдельным кабелем (рис. 2).
Количество квартир на каждом этаже равно K (для упрощения предполагается, что квартиры расположены на одной прямой, и нумерация квартир идет слева направо). Расстояние между соседними квартирами и этажами одинаково. Схема дома представлена в виде прямоугольной таблицы, строки которой – этажи, а столбцы – квартиры. Квартиры, имеющие компьютер, обозначаются 1 (единицей), а остальные 0 (нулями). Переход с одного этажа на другой возможен только в ближайшие квартиры (рис. 3).

2-ая картинка к задаче INTERNETомания

Требуется написать программу, которая выведет номера квартир, начиная с первого этажа, в которых должны быть расположены HUB’ы, при условии, что общая длина кабеля, соединяющего все компьютеры в сети, минимальна.

Примечание.  Концентраторы (HUB) могут располагаться и в тех квартирах, где нет компьютеров.

Формат входных данных:
Входные данные расположены в текстовом файле с именем INPUT.TXT в следующем порядке:

Формат выходных данных:
Результат работы программы выводится в файл OUTPUT.TXT и представляет собой последовательность номеров квартир, начиная с первого этажа, в которых должны располагаться концентраторы HUB.1

Пример файла INPUT.TXT (рис. 4):

5 3

1 1 0                {5-й этаж}

0 0 1                {4-й этаж}

1 1 1                {3-й этаж}

0 1 0                {2-й этаж}

1 0 0                {1-й этаж}

 

Пример файла OUTPUT.TXT
  1
  5
  8
  12
  14 

Указание. Задача легко сводится к предыдущей.

Еще одно из сравнительно новых применений динамического программирования – молекулярная биология, где базы данных содержат биологические последовательности (ДНК, РНК, белков), насчитывающие в сумме многие миллионы «букв», так что в их анализе не обойтись без компьютеров. Молекулы ДНК, содержащие генетическую информацию, - это длинные слова из четырех букв (А, Г, Ц, Т). В процессе эволюции, в результате мутаций последовательности меняются: одна буква может замениться на другую, выпасть, а может добавиться новая. Возникают жизненно важные задачи: насколько похожи два фрагмента, каким наименьшим числом превращений можно один из них получить из другого, найти подпоследовательность наибольшей длины, входящую в тот или другой фрагмент.

Задача 4. Заданы два слова длиной N и M соответственно (N, M \le 30). Найти подпоследовательность максимальной длины, входящую в то и другое слово (подпоследовательность не является непрерывной).

Задачи на динамическое программирование

Числа Фибоначчи

Вычислить $N$-ое число в последовательности Фибоначчи, — 1, 1, 2, 3, 5, 8, … — в которой первые два члена равны единице, а все остальные представляют собой сумму двух предыдущих.
Входные данные: Целое: $1 \le N \le 70$.
Выходные данные: Одно число — $N$-ый член последовательности.

Решение: условие задачи в виде формулы (динамический переход): $F_1=1$, $F_2=2$, $F_i=F_{i-1}+F_{i-2}, i \ge 3$.

const MaxN = 70;
var i,N : Integer;
    F : Array [1..MaxN] of Int64; { Массив для хранения вычисленных значений F }
begin
  { Чтение входных данных }
  Read(N);
  { Проверка корректности входных данных }
  assert( (1 <= N) and (N <= MaxN) );
  { Инициализация "динамики", первые 2 значения F }
  F[1]:=1;  F[2]:=1;
  { Общий шаг динамики: в цикле вычисляем значения F }
  for i:=3 to N do
    F[i] := F[i-1] + F[i-2];
  { Вывод результата }
  write(F[N]);
end.

Мячик на лесенке

На вершине лесенки, содержащей $N$ ступенек, находится мячик, который начинает прыгать по ним вниз, к основанию. Мячик может прыгнуть на следующую ступеньку, на ступеньку через одну или через 2. (То есть, если мячик лежит на 8-ой ступеньке, то он может переместиться на 5-ую, 6-ую или 7-ую.). Определить число всевозможных "маршрутов" мячика с вершины на землю.
Входные данные: Целое: $1 \le N \le 40$.
Выходные данные: Одно число — количество маршрутов.

Входной файл Выходной файл
4 7
39 12960201916
13 1705

Решение:
$D_i$ - количество всевозможных способов попасть на $i$-ую ступеньку.
$D_1=1$ - только один способ попасть на первую ступеньку.
$D_2=2$ - попасть на 2-ую можно 2-мя способами: прыгнув на неё сразу или прыгнув с 1-ой ступеньки.
$D_3=4$ - попасть на 3-ую можно 4-мя способами:
1) прыгнув на неё сразу
2) прыгнув на 1-ую, потом на 3-ю
3) прыгнув на 2-ую, потом на 3-ю
3) прыгнув на 1-ую ступеньку, потом на 2-ую, потом на 3-ю.
$D_i=D_{i-1}+D_{i-2}+D_{i-3}, i \ge 4$ - на ступеньки начиная с 4-ой можно попасть с 3-х предыдущих. Мы знаем, сколькими способами можно попасть на каждую ступеньку из предыдущих. Общий принцип: Количество вариантов как попасть в какое-то состояние равно сумме количеств вариантов как попасть в предыдущие состояния.

var I,N : integer;
    D : array [1..40] of Int64;
begin
  read(N);
  D[1]:=1; D[2]:=2; D[3]:=4;
  for I:=4 to N do
    D[I] := D[I-1] + D[I-2] + D[I-3];
  writeln(D[N])
end.

Черепашка

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

Вход: Первая строка — $N$ — размер доски ($1 \leq N \leq 50$).
Далее следует $N$ строк, каждая из которых содержит $N$ целых чисел, представляющие числа на доске.
Выход: Одно число — максимальная сумма.
Тест Ответ

3
2 6 0
5 0 6
4 1 2

16

Решение: $S_{ij}$ - максимальная набранная сумма, если черепашка находится в клетке $i,j$
Динамический переход: $S_{ij}=max(S_{i-1,j},S_{i,j-1})+A_{i,j}$
Инициализация: $S_{i,0} = 0$ $S_{0,j} = 0$
Ответ: $S_{N,N}$

Робот

В исследовательской лаборатории фирмы Robots&Co разработали новую модель робота. Главной особенностью данной модели робота является то, что он работает по заранее заданной программе, в которой могут присутствовать команды: сделать шаг на Юг, на Север, на Восток или на Запад. Робот исполняет программу строго последовательно и, дойдя до конца программы, останавливается. Специалисты из Robots&Co заинтересовались вопросом, сколько существует различных программ, состоящих из K инструкций, таких, что робот, выйдя из начала координат, придет в точку с координатами (X, Y). Оси координат располагаются параллельно сторонам света, и единица измерения, соответствует одному шагу робота. Напишите программу, которая дает ответ на этот вопрос.
Входные данные:
Во входном файле находятся три числа K, X и Y (0 \le K \le 16, |X|, |Y| \le 16), разделенные пробелами.
Выходные данные:
В выходной файл ваша программа должна поместить одно число — количество программ для робота.


e.in

e.out

5 -2 -3

10

16 16 15

0

16 3 3

56216160

Решение:
 - количество программ для робота состоящих из K инструкций, таких, что робот, выйдя из начала координат, придет в точку с координатами (X, Y)

Взрывоопасность
При переработке радиоактивных материалов образуются отходы двух видов — особо опасные (тип A) и неопасные (тип B). Для их хранения используются одинаковые контейнеры. После помещения отходов в контейнеры, последние укладываются вертикальной стопкой. Стопка считается взрывоопасной, если в ней подряд идет более двух контейнеров типа A. Для заданного количества контейнеров N определить число безопасных стопок.
Входные данные:
Одно число 1 \le N \le 30.
Выходные данные:
Одно число — количество безопасных вариантов формирования стопки.


f.in

f.out

5

8

Решение:


К-ичные числа
Требуется вычислить количество N-значных чисел в системе счисления с основанием K, таких, что их запись не содержит двух подряд идущих нулей. (2 \le K \le 10, N + K \le 18).
Входные данные:
Числа N и K в десятичной записи, разделенные пробелом или переводом строки.
Выходные данные:
Искомое число в десятичной записи.


g.in

g.out

4 3

44

5 2

8

Решение:

Для  будет:

Динамический переход:

Паровозики
$N$ локомотивов, имеющих номера от 1 до $N$ и установленных на железнодорожную колею, начинают двигаться в одну сторону, причем локомотив номер $k$ изначально движется со скоростью $k$ км/ч. Если локомотив, движущийся с большей скоростью, нагоняет более медленный локомотив, дальше они движутся один за другим со скоростью впереди идущего локомотива. Очевидно, через некоторое время после начала движения локомотивы разобьются на несколько групп, движущихся с разной скоростью.
Написать программу, определяющую, сколько начальных расстановок $s$ из $N!$ возможных дадут в результате p групп движущихся локомотивов.

Входные данные:
Два числа — $1 \le N \le 16$ и $1 \le p \le N$.
Выходные данные:
Одно число — $s$.


h.in

h.out

4 2

11

5 4

10

6 6

1

uses SysUtils;

const Nmax=16;

var
  Save : array [1..nmax,1..nmax,1..nmax] of Int64;

function F( i,n,p:byte; x:integer ):Int64;
{ i-ый паровоз впереди,
  n паровозов всего
  p групп }
var j:byte;
begin
  if Save[i,n,p]=-1 then begin
    if x=0 then writeln( Format('F(i=%d,n=%d,p=%d)=',[i,n,p] ) );
    Result := 0;
    { Рассмотрим, как может i-ый паровозик оказаться впереди }
    { При этом есть 2 варианта: }
    {  1. Он уйдёт вперёд и образует новую группу }
    {    Т.е. он быстрее предыдущего паровозика }
    {    Пусть предыдущий едет со скоростью j }
    { Мы могли получить это из ситуации:
       j in [1..i-1]
       n-1 паровоз всего;
       p-1 группа всего;
       т.е. новый паровоз образовал новую группу }
    for j:=1 to i-1 do begin
      Result := Result + F(j,n-1,p-1,x+1);
      if x = 0 then writeln( Format('  + F(i=%d,n=%d,p=%d)=%d',[j,n-1,p-1,F(j,n-1,p-1,x+1)] ) );
    end;
    {   i-ый не образует новую группу, т.е. j>i }
    for j:=i+1 to n do begin
      Result := Result + F(i,n-1,p,x+1);
      if x = 0 then writeln( Format('  + F(i=%d,n=%d,p=%d)=%d *',[i,n-1,p,F(i,n-1,p,x+1)] ) );
    end;
    Save[i,n,p] := Result;
  end;
  F:=Save[i,n,p];
  if (i>n) or (p>n) then
    assert( Result = 0 );
end;

var i,j,NN,PP,n,p:byte;
    s:Int64;
begin
  readln(NN,PP);
  for i:=1 to NN do
    for n:=1 to NN do
      for p:=1 to NN do begin
        Save[i,n,p]:=-1;
        if (i>n) or (p>n) then Save[i,n,p]:=0;
        { Одна группа - и не первый впереди - не может быть :) }
        if (p=1) and (i<>1) then Save[i,n,p]:=0;
        { Кол-во групп = кол-ву паровозов и первый - самый быстрый }
        if (n=p) and (i=n) then Save[i,n,p]:=1;
      end;
  for i:=1 to NN do
    for n:=1 to NN do
      for p:=1 to NN do begin
        if (n=p) and (i<n) then assert( F(i,n,p,1) = 0 );
        if i>n then assert( F(i,n,p,1) = 0,
          Format('F(%d,%d,%d)=%d',[i,n,p,F(i,n,p,1)]) );
      end;
  s:=0;
  for i:=1 to NN do
    s:=s+F(i,NN,PP,1);
  writeln(s);
end.