CSES - Dynaaminen ohjelmointi 2/2

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 {c1,c2,,ck}\{c_1,c_2,\dots,c_k\}. Tehtävänä on laskea, monellako tavalla kolikoista voi muodostaa rahamäärän nn, kun jokaista kolikkoa saa käyttää miten monta kertaa tahansa.

Esimerkiksi jos kolikot ovat {2,3,5}\{2,3,5\} ja n=7n=7, vastaus on 55, koska vaihtoehdot ovat 2+2+32+2+3, 2+3+22+3+2, 3+2+23+2+2, 2+52+5 ja 5+25+2.

Ratkaisu

Voimme ratkaista tehtävän kahdella for-silmukalla, joista ulompi käy läpi kaikki rahamäärät 1n1 \dots n ja sisempi käy läpi kolikot. Laskemme jokaiselle rahamäärälle, monellako tavalla sen voi muodostaa kolikoista.

Seuraavassa koodissa d[x]d[x] ilmaisee, monellako tavalla rahamäärän xx voi muodostaa kolikoista. Alussa asetetaan d[0]=1d[0]=1, 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 2+2+32+2+3 ja 2+3+22+3+2 ovat oikeastaan samat, koska kolikot ovat samat mutta ne ovat vain eri järjestyksessä.

Tässä muunnelmassa esimerkkitapauksen vastaus on 22, koska erilaiset tavat ovat 2+2+32+2+3 ja 2+52+5.

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 dd kertoo jokaisesta rahamäärästä, monellako erilaisella tavalla kyseisen rahamäärän voi muodostaa käyttämällä siihen asti käsiteltyjä kolikkoja.