DAG模型——硬幣問題

賈樹丙發表於2013-09-07

硬幣問題  

  有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 }         

 

相關文章