1. 程式人生 > >動態規劃思想之最小硬幣分配數

動態規劃思想之最小硬幣分配數

動態規劃演算法,第一次接觸大概也是一年多前了,那會為了參加個ACM競賽,倉促看了下概念,之後由於去搞各種開發又對演算法不了了之了。最近深感自己內功的薄弱,準備再次進軍下演算法部分,今天就以一個簡單的OJ題練練手,好好體會下DP思想,並留下點感想,可以給自己日後回顧複習。下面是題目的簡介:

問題描述

作為老師,最盼望的日子就是每月的10號了,因為這一天是發工資的日子,養家餬口就靠它了,但是對於學校財務處的工作人員來說,這一天則是很忙碌的一天,財務處的小胡老師最近就在考慮一個問題:如果每個老師的工資額都知道,最少需要準備多少張人民幣,才能在給每位老師發工資的時候都不用老師找零呢?這裡假設老師的工資都是正整數,單位元,人民幣一共有100元、50元、10元、5元、2元和1元六種。
輸入

輸入資料包含多個測試例項,每個測試例項的第一行是一個整數n(n<100),表示老師的人數,然後是n個老師的工資。n=0表示輸入的結束,不做處理。
輸入樣例

3
1 2 3
0

輸出樣例

4

分析

先自己規定一個數學表示式,d(i)=j代表的實際意義是:如果需要分配i塊錢至少需要j個硬幣。那麼可以很容易的知道d(0) = 0;d(1) = d(1 - 1) + 1;d(2) = min{d(2-2) + 1,d(2 - 1) + 1}.這裡以d(2)為例,做個簡單的分析,如果需要2塊錢,那麼我們可以將這個問題分解成更小的子問題來求解,即可以在d(1)的基礎上在拿1個硬幣就可以湊到2塊錢,或者是在d(2 - 2)的基礎上再拿一個2元的硬幣即可達到要求。所以,這個分錢求數目的過程可以通過其子問題來間接求解,這也就是說這個問題是具有最優子結構的。我們也很容易的得出這個問題的一個狀態轉移方程為d(i) = min{d(i - v(j)) + 1}。v(j)指最小直接可用的硬幣價值。


當然,上面那個轉移方程和遞迴式很像,但如果採用遞迴來求解,那麼動態規劃的優勢或許就體現不出來了,在我目前的理解下,我認為動態規劃的魅力就在於求解此類問題時能大大縮短演算法的耗時,因為狀態轉移方程的存在,所以我們完全可以利用一個陣列來儲存之前求得的d(i)的值,也就是自底向上的求解,這樣就避免了類似遞迴,減少了重複的呼叫次數,所以提高了演算法的執行效率。

示例程式碼

#include <stdio.h>

int wage[] = {1,2,5,10,50,100};

int main()
{
    int n,i,j,num;
    int perWage[200];

    int
Min = 1000000; int result[1000] = {0}; for(i = 1; i < 1000; i ++) { Min = 1000000; for(j = 0; j < 6; j++) { if(i - wage[j] >= 0) { num = result[i - wage[j]] + 1; if(num < Min) Min = num; } } result[i] = Min; } while(scanf("%d",&n)) { if(n == 0) break; for(i = 0; i < n; i++) scanf("%d",&perWage[i]); int num = 0; for(i = 0; i < n; i++) { num += result[perWage[i]]; } printf("%d\n",num); } return 0; }