關於一些基礎的dp——硬幣的那些事(dp的基本引入)

江上舟搖發表於2022-02-13

1.最少硬幣問題大體題意:

有n種硬幣,面值分別是v1,v2......vn,數量無限,輸入一個非負整數s,選用硬幣使其和為s,要求輸出最少的硬幣組合。

我們可以這樣分析:

定義一個名為Min[s]的陣列來表示是金額s所對應的最少硬幣的組合,所以對我們來說,只要是在程式中查到Min[i]的大小就可以知道最少的硬幣組合是多少了。

以面值為{1,5,10,25,50}為例子來講一下,方便以後備賽。

假如我們輸入的s是100,當全用1coin的時候,如圖:(畫的很拙劣,抱歉)

那麼第一個格子裡指的就是當金額為0的時候所需要的硬幣數是0,那當金額為1的時候Min[1]=Min[1-1]+1,以此類推,當然,這是隻使用硬幣面值為1的時候。

當我們加入了面值5,就變成了這樣子:

我們可以看到,到了5的時候,選擇就變成了兩種——一個是5個1元的硬幣,另一個是直接一個5元的硬幣,可以這麼理解:

Min[5]=min(Min[5],Min[5-5]+1),這樣才能保證硬幣數是最小的。

我們還有其他面值的硬幣,當然也要一一的引入。

所以說,我們在dp中將Min[i]這種記錄問題最優解的資料叫做“狀態”,從Min【i-1】,Min【i-5】這種式子叫做狀態轉移,在問題中,我們可以清晰的看見動態規劃往往是利用問題前面的狀態,也就是利用子問題的關聯性去解決問題,這是dp的一大特點。

本題題解如下:

 1 #include<bits/stdc++.h>
 2  using namespace std;
 3  int Min[251];//每個金額所對應的最少的硬幣數 
 4  int coin[5]={1,5,10,25,50};//5種金額 
 5  int Min_path[251]={0}; 
 6  void ans()
 7  {
 8      for(register int k=0;k<251;k++)
 9      Min[k]=INT_MAX;//定義初始值是無窮大 
10      Min[0]=0;
11      for(register int j=0;j<5;j++)
12      
13          for(register int i=coin[j];i<251;i++)
14          {
15              Min_path[i]=coin[j];
16              Min[i]=min(Min[i],Min[i-coin[j]]+1);
17         }
18 } 
19 /*void print_ans(int *coin_path,int s)//列印組合 
20 {
21     while(s)
22     {
23         cout<<Min_path[s]<<' ';
24         s=s-Min_path[s];
25     }
26 }*/
27  int main()
28 {
29     ios::sync_with_stdio(false);
30     int s;
31     ans();//打表 
32     cin>>s;
33     cout<<Min[s]<<endl;
34     //print_ans(Min_path,s);
35      return 0;
36 } 

但是對於所有硬幣問題,就不能這麼解了。

2.所有硬幣問題大體題意如下(HDUOJ2069,但是HDUOJ進不去了):

有n種硬幣,面值依舊是前面那些巴拉巴拉,到最後就不一樣了,要求輸出所有硬幣組合。

我們這一局採用dp來解,用dp[i][j]來表示當金額為i的時候最少需要j枚硬幣。

dp[0][0]是0,那dp[1][1]就是dp[1-1][1-1]+1,畫圖表示一下:

那我們可以一次類推dp[i][j]=min(dp[i][j],dp[i-coin[k]][j-1]

可以看到,dp[i][j]縱座標的結果相加就是最少硬幣的組合。

程式碼如下:

 1 #include<bits/stdc++.h>
 2  using namespace std;
 3  int ans[251]; 
 4  int coin[5]={1,5,10,25,50};
 5  int dp[251][101];
 6  void solve()
 7  {
 8      dp[0][0]=1;//0元0個硬幣算是一種方案 
 9      for(int i=0;i<5;i++)
10      {
11          for(int j=1;j<101;j++)//硬幣數不超過100 
12          {
13              for(int k=coin[i];k<251;k++)
14              {
15                  dp[k][j]+=dp[k-coin[i]][j-1];
16             }
17         }
18     }
19  }
20  int main()
21  {
22      int s;
23      solve();
24     cin>>s; 
25     for(int i=0;i<251;i++)
26     {
27         for(int j=0;j<101;j++)
28         {
29             ans[i]+=dp[i][j];
30         }
31     }
32     cout<<ans[s]<<endl;
33  }

 

這就是基礎的dp引入,硬幣問題就解決了。

sdutoj

有一個最少硬幣問題:https://acm.sdut.edu.cn/onlinejudge3/problems/1725?from=%2Fsets%2F17

但這個就不是dp的簡單引入了,這個屬於多重揹包啦~~~

相關文章