硬幣問題
有n種硬幣,面值分別為V1,V2,...,Vn,每種都有無限多。給定非負整數S,可以選用多少個硬幣,使得面值之和恰好為S?輸出硬幣數目的最小值和最大值。1<=n<=100, 0<=S<=10000,1<=Vi<=S.
分析:
我們把每種面值看做一個點,表示“還需要湊足的面值”,則初始狀態為S,目標狀態為0.若當前在狀態 i ,每使用一個硬幣j ,狀態便轉移到 i-Vj .
注意到最長路和最短路的求法是類似的,下面只考慮最長路。由於終點固定,d(i)的確切含義變為“從結點i出發到結點0的最長路徑長度”
在記憶化搜尋中,如果用特殊值表示“還沒算過”,則必須將其和其他特殊值(比如無解)區分開。
也可以不用特殊值表示還沒算過,而是用另外一個陣列vis[i]表示狀態i “是否被訪問過”
記憶化搜尋程式碼如下:
1 #include<cstdio> 2 #include<cstdlib> 3 #include<algorithm> 4 using namespace std; 5 const int maxn = 102, maxv = 10005; 6 int n, S, v[maxn], mind[maxv], maxd[maxv]; 7 int dp1(int s) //最小值 8 { 9 int &ans = mind[s]; 10 if(ans != -1) return ans; 11 ans = 1<<30; 12 for(int i = 1; i <= n; ++i) if(v[i] <= s) ans = min(ans, dp1(s-v[i])+1); 13 return ans; 14 } 15 int vis[maxv]; 16 int dp2(int s) //最大值 17 { 18 if(vis[s]) return maxd[s]; 19 vis[s] = 1; 20 int &ans = maxd[s]; 21 ans = -1<<30; 22 for(int i = 1; i <= n; ++i) if(s >= v[i]) ans = max(ans, dp2(s-v[i])+1); 23 return ans; 24 } 25 void print_ans(int* d, int s) //列印的是邊 26 { 27 for(int i = 1; i <= n; ++i) if(v[i] <= s && d[s-v[i]]+1 ==d[s]) 28 { 29 printf("%d ",i); 30 print_ans(d, s-v[i]); 31 break; 32 } 33 } 34 int main() 35 { 36 freopen("9-3.in", "r", stdin); 37 scanf("%d%d", &n, &S); 38 for(int i = 1; i <= n; ++i) scanf("%d", v+i); 39 memset(mind, -1, sizeof(mind)); 40 mind[0] = 0; 41 printf("%d\n", dp1(S)); 42 print_ans(mind, S); 43 printf("\n"); 44 memset(maxd, -1, sizeof(maxd)); 45 memset(vis, 0, sizeof(vis)); 46 maxd[0] = 0; 47 vis[0] = 1; 48 printf("%d\n", dp2(S)); 49 print_ans(maxd, S); 50 printf("\n"); 51 return 0; 52 }
遞推程式碼如下:
1 #include <cstdio> 2 #include <cstring> 3 const int maxn = 110, maxv = 10086, INF = 1234567890; 4 int v[maxn], S, n, maxf[maxv], minf[maxv], min_coin[maxv], max_coin[maxv]; 5 void print_ans(int *d, int s){ 6 while(s){ 7 printf("%d ", d[s]); 8 s-=v[d[s]]; 9 } 10 } 11 int main(){ 12 freopen("9-3.in", "r", stdin); 13 scanf("%d%d", &n, &S); 14 for(int i = 1; i <= n; ++i) scanf("%d", &v[i]); 15 for(int i = 1; i <= S; ++i) { 16 //最應注意的是邊界條件、初始化,因為遞推作為重點一般不會寫錯,就是在這種細節處出錯 17 maxf[i] = -INF; 18 minf[i] = INF; 19 } 20 maxf[0] = minf[0] = 0; 21 for(int i = 1; i <= S; ++i) 22 for(int j = 1; j <= n; ++j) if(i >= v[j]){ 23 if(maxf[i-v[j]] + 1 > maxf[i]){ 24 maxf[i] = maxf[i-v[j]] + 1; 25 max_coin[i] = j; 26 } 27 if(minf[i-v[j]] + 1 < minf[i]){ 28 minf[i] = minf[i-v[j]] + 1; 29 min_coin[i] = j; 30 } 31 } 32 printf("%d\n", minf[S]); 33 print_ans(min_coin, S); 34 printf("\n"); 35 printf("%d\n", maxf[S]); 36 print_ans(max_coin, S); 37 printf("\n"); 38 return 0; 39 }
不必列印路徑程式碼:
1 #include<cstdio> 2 #include<cstdlib> 3 const int maxn = 110, maxv = 10086, INF = 123456789; 4 int n, S, v[maxn], min[maxv], max[maxv]; 5 int main(){ 6 scanf("%d%d", &n, &S); 7 for(int i = 1; i <= n; ++i) scanf("%d", v+i); 8 min[0] = max[0] = 0; 9 for(int i = 1; i <= S; ++i) {min[i] = INF; max[i] = -INF;} 10 for(int i = 1; i <= S; ++i) 11 for(int j = 1; j <= n; ++j) if(v[j] <= i){ 12 if(min[i-v[j]] + 1 < min[i]) min[i] = min[i-v[j]] + 1; 13 if(max[i-v[j]] + 1 > max[i]) max[i] = max[i-v[j]] + 1; 14 } 15 printf("%d\n", min[S]); 16 printf("%d\n", max[S]); 17 return 0; 18 }