動的計画法 c言語
【問題】
額面がB1, B2,..., Bm 円のm種類の紙幣を使って総額n円を支払うときの紙幣の最小枚数を求なさい。各額面の紙幣は何度でも使用できる。
入力
最初の行に整数nと整数m、次の行に空白区切りでm種類の紙幣の額面
n m
B1 B2 ... Bm
出力
必要な紙幣の最小枚数を出力して改行して終了
制約
・1 ≦ n ≦ 50,000
・1 ≦ m ≦ 20
・1 ≦ 額面 ≦ 10,000
・額面はすべて異なり必ず1を含む
入力例
55 4 1 5 10 50
出力例
2
【解法】
今回は動的計画法でこちらの問題をアプローチします。
まず動的計画法とは、
下記2条件を満たすアルゴリズムの総称である。 1. 分割統治法:部分問題から問題全体を解く 2. メモ化:部分問題の計算結果を再利用する
今回は1の分割統治ほうを用いて解法を説明する。
まず部分問題とはいわゆる数列でいう一般解のようなもので
an = an-1 +1
今回の部分問題は
T[i][j]: i番目までの紙幣を用いてj円払うときの紙幣の最小枚数
とTを定義する。
また
B[i]: 各紙幣Bの額面の配列
とBを定義する。
Tの次のTを求めると
T[i][j]に関してB[i]を使うか、使わないかの2択があり、それらの小さい値を用いる。
・B[i]を使用するパターン
T[i-1][j]
・B[i]を使用しないパターン
T[i][j – B[i]] + 1
以上より
T[i][j] = min(T[i-1][j], T[i][j – B[i]] + 1)
と部分問題を求めることができる。
答えはT[n][m]で求めることができる。
【コード】
#include <stdio.h> void s_data(int m, int data[60000]){ int i,j, tmp; for(i=0;i<m;i++){ for(j=i+1;j<m;j++){ if(data[i]>data[j]){ tmp = data[i]; data[i] = data[j]; data[j] = tmp; } } } return; } int min(int a, int b){ if(a > b){ return b; }else{ return a; } } int main(void){ int n, m ,data[60000],i,j,count=0, T[60][60000]; scanf("%d %d",&n,&m); for(i=0;i<m;i++){ scanf("%d", &data[i]); } s_data(m, data); T[0][0] = 0; for(j=1;j<=n;j++){ T[0][j] = T[0][j-data[0]]+1; } for(i=1;i<m;i++){ for(j=0;j<=n;j++){ if(j-data[i] >= 0){ T[i][j] = min(T[i-1][j], T[i][j-data[i]]+1); }else{ T[i][j] = T[i-1][j]; } } } printf("%d\n", T[m-1][n]); return 0; }