2.  Динамично оптимиране - оптимизационни задачи

** Задача за монети - минимален брой [Coin problem, AL p. 65]
Дадени са монети със стойности c1, c2, ..., ck и целева сума n. Задачата е да се направи тази сума с минимален брой монети.

Пример: Дадени са три вида монети със стойности 1, 3 и 4.
solve(0) = 0
solve(1) = 1
solve(2) = 2
solve(3) = 1
solve(4) = 1
solve(5) = 2
solve(6) = 2
solve(7) = 2
solve(8) = 2
solve(9) = 3
solve(10) = 3

Рекурсивна формула за примера:
solve(x) = min(solve(x−1) + 1, solve(x−3) + 1, solve(x−4) + 1)

solve(10) = solve(7) + 1 = solve(4) + 2 = solve(0) + 3 = 3.

Обща рекурсивна формула:
solve(0) = 0, solve(x) = min {solve(x - ci) + 1, i = 1, 2,..., k}

int solve(int x)
{
    if (x == 0) return 0;
    int best = INF;
    for (int i = 0; i < k; i++)
        if (x - c[i] >= 0) best = min(best, solve(x - c[i]) + 1);
    return best;
}

Със запомняне на пресметнатите стойности (memorization):
int solve(int x)
{
    if (x == 0) return 0;
    if (value[x]  >= 0) return value[x];
    int best = INF;
   for (int i = 0; i < k; i++)
        if (x - c[i] >= 0) best = min(best, solve(x - c[i]) + 1);
    value[x] = best;
    return best;
}

Итеративен вариант:
value[0] = 0;
for (int x = 1; x <= n; x++)
{
    value[x] = INF;
    for (int i = 0; i < k; i++)
        if (x - c[i] >= 0) value[x] = min(value[x], value[x - c[i]] + 1);
}

Това решение получава с колко монети може да се направи сумата. С кои монети е това решение може да се намери със следната модификация:

int first[N];
value[0] = 0;
for (int x = 1; x <= n; x++)
{
    value[x] = INF;
    for (int i = 0; i < k; i++)
        if (x-c >= 0 && value[x-c]+1 < value[x])
    {
        value[x] = value[x-c[i]]+1;
        first[x] = c[i];
    }
}

while (n > 0)
{

    cout << first[n] << "\n";
    n -= first[n];
}


** Задача за монети - брой разбивания [8.3.4]
Дадени са n типа монети със стойности съответно: c0, c1, ..., cn–1, и естествено число s. Да се намери броят на различните представяния на s с монети измежду наличните типове. Стойностите c0, c1, ..., cn–1 са цели положителни числа. Всеки тип монети може да участва в сумата неограничен брой пъти.

F(s, m) са броя на начините, по които можем да представим сумата s с тези монети, чиято стойност не надвишава m.
F(s, m) = 0, s =0;
F(s, m) = F(s, s), s < m;
F(s, m) = 1 + sum{ F(s - i, i): i=1, 2, ..., m,    има k: ck = i} за s = m и има k: ck = s;
F(s, m) = sum{ F(s - i, i): i=1, 2, ..., m,    има k: ck = i} иначе

#include<iostream>
#define MAXCOINS 100 /* Максимален брой монети */
#define MAXSUM 100   /* Максимална сума */

using namespace std;

unsigned long F[MAXSUM][MAXSUM]; /* Целева функция */
unsigned char exist[MAXSUM];     /* Съществува ли монета с такава стойност */
unsigned coins[MAXCOINS] = {1,2,3,4,6}; /* Налични типове монети */
unsigned sum = 6;               /* Сума, която искаме да получим */
const unsigned n = 5;           /* Общ брой налични монети */

/* Инициализираща функция */
void init(void)
{
    unsigned i, j;
    /* Нулиране на целевата функция */
    for (i = 0; i <= sum; i++)
        for (j = 0; j <= sum; j++) F[i][j] = 0;
  
    /* Друго представяне на стойностите на монетите за по-бърз достъп */
    for (i = 0; i <= sum; i++) exist[i] = 0;
    for (i = 0; i < n; i++) exist[coins[i]] = 1;
}

/* Намира броя на представянията на sum */
unsigned long count(unsigned sum, unsigned max)
{
    unsigned long i;
    if (sum <= 0) return 0;
    if (F[sum][max] > 0) return F[sum][max];
    else
    {
        if (sum < max) max = sum;
        if (sum == max && exist[sum]) /* Има монета с такава стойност */
            F[sum][max] = 1;
   
        for (i = max; i > 0; i--) /* Пресмятаме всички */
            if (exist[i]) F[sum][max] += count(sum - i, i);
    }
    return F[sum][max];
}

int main()
{
    init();
    cout << "Sum " << sum << "   Num " << count(sum, sum) << endl;
    return 0;
}

Домашно - задачи 1 и 2