Содержание:
Задача о сдаче
В предыдущем примере решение задачи при помощи ДП было излишним и искусственным. Но для простоты мы взяли эту задачу.
Решим еще одну задачу чуть более сложную.
n копеек.
* Примечание: 1. порядок монет в сдаче важен, 2. монет можно использовать неограниченное количество

Разбор задачи:
Предположим нужно вернуть сдачу 4 копейки. Выделим всевозможные способы:
1 1 1 11 33 1
Заметим, что 2 и 3 способ — это разные способы, т.к. порядок в нашей задаче важен.
Ответим на вопрос, сколько способов для того, чтобы вернуть:
- 1 копейку? — 1 способ
- 0 копеек? — 1 способ (каждого номинала монет необходимо вернуть по 0 способу)
В этой задаче ДП — не самый эффективный способ, но решим при помощи него.
Решение:
Так как важен порядок монет, то решим так:
1 этап: Обозначение целевой функции
Обозначим через F(i) кол-во способов вернуть сдачу размера i (F(n) — целевая функция — ответ на нашу задачу, т.е. кол-во способов вернуть сдачу размера n)
2 этап: Определение порядка решения
Порядок решения: Выполнять задачу будем линейно, т.е. начиная с самой простой и постепенно усложняя ее (с F(1) до F(n)). Хотя можно решать задачи ДП и не линейно, а, например, при помощи дерева.
Начнем с самой простой подзадачи. Предположим, что существует только монета номиналом 1 копейка. F(1) = 1.
Результат функции F(1) мы знаем, он равен 1; используя знания о F(1), вычислим F(2), используя знания о F(2), найдем F(3) …. и т.д.
F(1) <- F(2) <- F(3) ... F(n-1) <- F(n)
*можно в самом начале добавить F(0), F(0)=1.
3 этап: Определение начальных условий
F(0) и F(1)), иначе у рекурсии не будет условия для остановки. Этот этап и называется определение начальных условийИтак получаем:
F(0)= 1 способ вернуть 0 копеекF(1)= 1 способ вернуть 1 копейкуF(2)=F(1)- 1 способ вернуть 2 копейкиF(3)= 2 способа (F(2) + F(0))- 1 копейка возвращается + 2 копейки (т.е. 2 копейки - функция
F(2)) - 3 копейки возвращаются + 0 копеек (т.е. 0 копеек - ф-ция
F(0)) F(4)=F(3)+F(1)= 2 + 1 = 3 способа- сначала отдаем 1 копейку + остается вернуть 3 (=
F(3)) - сначала отдаем 3 копейки + остается вернуть 1 (=
F(1)) 1 1 1 11 33 1F(5)=F(4)+F(2)+F(0)= 3 + 1 + 1 = 5 способов
Здесь расшифруем: 2 копейки получим так: 1 копейка возвращается + 1 копейку осталось вернуть сдачей (F(1)), т.е. один способ
Расшифруем:
ИЛИ
Получаем:
F(3) = F(2) + F(0) = 1 + 1 = 2 способа - > стоит сложение потому что логическое ИЛИ

Пояснение к рисунку: в прямоугольниках обозначены функции, в окружностях - монеты.
Расшифруем:
т.е. как мы и рассчитывали в начале занятия:
F(6), F(7), F(10))4 этап: Вычисление и нахождение общей формулы
Напишем формулу в общем виде для F(k), где k >= 10
Рассмотрим способы:
- даем сдачу 1 копейка + остается дать сдачу
k-1копейка - даем сдачу 3 копейки + остается дать сдачу
k-3копейки - даем сдачу 5 копеек + остается дать сдачу
k-5копеек - даем сдачу 10 копеек + остается дать сдачу
k-10копеек
∨ (ИЛИ)
∨
∨
Получаем формулу в общем виде:
F(k) = F(k-1) + F(k-3) + F(k-5) + F(k-10)
Эта формула действительна для k >= 10
Аналогично посчитаем формулу для 5 <= k <= 10:
F(k) = F(k-1) + F(k-3) + F(k-5)
1 <= k <= 3 и для 3 <= k <= 5Таким образом, выделим этапы решения задачи:
- определение целевой функции:
F(i)- определение порядка решения:
F(0)<-F(1)<-F(2)<-F(3)...F(n-1)<-F(n)- определение начальных условий:
F(0) = 1,F(1) = 1,F(2) = 1,F(4) = F(3) + F(1),F(5) = F(4) + F(2) + F(0)- вычисление и нахождение общей формулы:
для k>=10:
F(k)=F(k-1)+F(k-3)+F(k-5)+F(k-10)
для 3 <= k < 5:f(k)=f(k-1)+f(k-3);
для 5 <= k < 10:f(k)=f(k-1)+f(k-3)+f(k-5);
k на языке программирования Pascal. Выполнить задания без использования рекурсии1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | var f: array[0..100]of integer; i,k:integer; procedure kol_sposobov(k: integer); begin // определение начальных условий: f[0]:=1; f[1]:=1; f[2]:=1; //порядок решения от простого к сложному, от f(3) до f(k) for i:=3 to k do begin // использование общей формулы для различных k if (i>=3) and (i<5) then f[i]:=f[i-1]+f[i-3]; if (i>=5) and (i<10)then f[i]:=f[i-1]+f[i-3]+f[i-5]; if (i>=10) then f[i]:=f[i-1]+f[i-3]+f[i-5]+f[i-10]; writeln (' для i=', i, ' способов=',f[i]); end; end; begin writeln ('введите размер сдачи k '); readln(k); kol_sposobov(k); end. |
Перевод в формулу индикатор
Функцию F(n) легко перевести в функцию-индикатор:
функция равняется событию, если оно произошло, и функция не равняется событию, если оно не произошло:

Переведем и получаем:
F(k) = F(k-10) * I(k>=10) + F(k-5) * I(k>=5) + F(k-3)* I(k>=3) + F(k-1) * I(k>=1) =
∑ F(k-i)* Ik>=i (i ∈ множество всех монет)
В случае, когда k>=10 получим:
Первое слагаемое: F(k-10) * на индикатор того, что k>=10, т.е. если в задаче действительно k>=10, то индикатор вернет единицу (истина), и первое слагаемое будет присутствовать в формуле;
Второе слагаемое: F(k-5) (оно должно присутствовать в формуле лишь тогда, когда k>=5) * на индикатор того, что k>=5, т.е. если в задаче действительно k>=5, то индикатор вернет единицу (истина), и второе слагаемое будет присутствовать в формуле;
… и .т.д.
Таким образом, используя функцию индикатор можно несколько формул (в нашем случае 4) заменить на одну формулу:
- формула для
n>=10,- формула для 5<=
n<=10,- формула для 3<=
n<=5,- формула для 1<=
n<=3заменили на формулу:
F(k) = F(k-10) * I(k>=10) + F(k-5) * I(k>=5) + F(k-3)* I(k>=3) + F(k-1) * I(k>=1)
или, если сжать формулу:
∑ F(k-i)* Ik>=i (i ∈ множеству всех монет, т.е. 1, 3, 5, 10)
F(0)=1, иначе рекурсивная функция будет зацикленнойСложность и эффективность решения
Попытаемся проанализировать эффективность нашего решения, т.е. его временные условия.
Мы сказали, что чтобы вычислить функцию F(n), нужно вычислить F(0), F(1), F(2) и т.д. Т.е. в нашей цепочке мы сделаем n переходов (F(0) <- F(1) <- F(2) <- F(3) … F(n-1) <- F(n))
А сколько «стоит» каждый переход? На каждом шаге мы считаем сумму, в которой максимум будет 4 слагаемых (т.е. 4 операции):
F(k) = F(k-10) * I(k>=10) + F(k-5) * I(k>=5) + F(k-3)* I(k>=3) + F(k-1) * I(k>=1)
Как правило, сложность работы алгоритма формулируется, как функция от длины входных данных алгоритма.
Что у нас является входными данными? — это набор монет (1, 2, 5, 10) и размер сдачи, который мы должны вернуть (например, 10 копеек).
Допустим, размер сдачи = n, тогда мы записываем log от n
Т.е. сложность нашего алгоритма: 𝜭(lg n)
Время работы данного алгоритма не линейно, а экспоненциально (достаточно «жадный» алгоритм). Т.е. выполненное решение с точки зрения занимаемого времени не эффективно.
n = 5, способы такие:
1 1 31 1 1 1 15
Решить методом динамического программирования.
Но существует и эффективное решение данной задачи, это уже задачи комбинаторики.
