Tällä viikolla jatkamme dynaamisen ohjelmoinnin harjoittelua. Tällä viikolla erityisesti Tirakirjan luvun 9 esimerkki repunpakkauksesta on hyödyllinen.
Seuraavaksi käymme läpi yhden lisäesimerkin dynaamisesta ohjelmoinnista.
Tehtävä
Annettuna on joukko kolikoita, joiden arvot ovat . Tehtävänä on laskea, monellako tavalla kolikoista voi muodostaa rahamäärän , kun jokaista kolikkoa saa käyttää miten monta kertaa tahansa.
Esimerkiksi jos kolikot ovat ja , vastaus on , koska vaihtoehdot ovat , , , ja .
Ratkaisu
Voimme ratkaista tehtävän kahdella for-silmukalla, joista ulompi käy läpi kaikki rahamäärät ja sisempi käy läpi kolikot. Laskemme jokaiselle rahamäärälle, monellako tavalla sen voi muodostaa kolikoista.
Seuraavassa koodissa ilmaisee, monellako tavalla rahamäärän voi muodostaa kolikoista. Alussa asetetaan , koska tyhjän rahamäärän voi muodostaa yhdellä tavalla (ei kolikoita).
d[0] = 1; for (int i = 1; i <= n; i++) { for (int j = 1; j <= k; j++) { if (i-c[j] >= 0) d[i] += d[i-c[j]]; } } cout << d[n] << "\n";
Muunnelma
Luonteva muunnelma tehtävälle on, että haluamme laskea vain aidosti erilaiset tavat. Esimerkiksi tavat ja ovat oikeastaan samat, koska kolikot ovat samat mutta ne ovat vain eri järjestyksessä.
Tässä muunnelmassa esimerkkitapauksen vastaus on , koska erilaiset tavat ovat ja .
Ratkaisu
Yllättävää kyllä, saamme ratkaistua muunnelman muuttamalla vain vähän aiempaa koodia: riittää vaihtaa keskenään silmukoiden rungot.
d[0] = 1; for (int j = 1; j <= k; j++) { for (int i = 1; i <= n; i++) { if (i-c[j] >= 0) d[i] += d[i-c[j]]; } } cout << d[n] << "\n";
Nyt koodi käy läpi kolikot yksi kerrallaan ja jokaisen kohdalla kaikki rahamäärät läpi. Jokaisen kolikon käsittelyn jälkeen taulukko kertoo jokaisesta rahamäärästä, monellako erilaisella tavalla kyseisen rahamäärän voi muodostaa käyttämällä siihen asti käsiteltyjä kolikkoja.