Coins Exchange 动态规划入门题目

题意:换零钱,求找开某个面值至少需要多少枚零钱

例如:有面值1,4,6的硬币无限枚,现需要找开面值8,有两种方案,1+1+6和4+4,则至少需要两枚零钱可找开8元面额。

 

题解:

(一)使用N^2的动态规划,略优于深度优先遍历

用opt[N]表示换零N面值至少需要的枚数,data[k]数组存放给定的k种硬币面额。

首先将opt[data[i]]全部置1,即至少需要1枚硬币可找零。其余面额都初始化成最大值INF。

从小到大,对面值逐个进行搜索,搜索面值N,从 1+(N-1) 搜索到 N/2+N/2。

状态转移方程

for (int i = min; i <= Max; ++i)
for (int j = min; j <= i; ++j)
    opt[i] = min(opt[j] + opt[i - j], opt[i]);

每次计算获得的新值可以更新更大的面值,符合动态规划所需的最优子结构和无后效性。


但是当N很大,k很小时,N^2的算法就会显得效率很低,例如仅有面值为1的硬币,拼凑10000面值,

10000 = 9999 + 1 = 9998 + 2 = 9997 + 3

以上其实是同一种方案。

 

(二)考虑状态之间的转换关系

对仅有一种零钱来讲,从之前所有状态只有一种方法能到达新状态,那就是添加一枚硬币。

即对9999添加一枚硬币得到10000,对于9998添加一枚,再添加一枚其实是重复劳动。

新状态与之前状态的联系是添加一枚硬币

这样能省去大量重复运算,复杂度为N*k。

//Coin Exchange
#include <stdio.h>
int T, N, Max, min;
int data[15], opt[64005];
int main(void){
    scanf("%d", &T);
    for (int test_case = 1; test_case <= T; ++test_case){
        for (int i = 0; i < 64005; ++i)opt[i] = 64005;
        min = 64005;
        scanf("%d", &N);
        for (int i = 0; i < N; ++i){
            scanf("%d", &data[i]);
            opt[data[i]] = 1;
            if (data[i] < min)min = data[i];
        }
        scanf("%d", &Max);
        for (int i = min + 1; i <= Max; ++i)
        for (int j = 0; j < N; ++j){
            if ((data[j]<i) && (opt[i]>opt[i - data[j]] + 1))
                opt[i] = opt[i - data[j]] + 1;
        }
        printf("Case #%d\n%d\n", test_case, opt[Max]);
    }
    return 0;
}

 

(三)另一种思路是考虑零钱种类

实现思路与过程来自01背包问题。

仅考虑一种零钱,得到每个面额的最优值。然后一个一个增加零钱种类,每增加一种零钱都刷新每个面额的最优值。

考虑当前面额由当前种零钱直接得到,是否能刷新当前面额的最优值。

与上一种思路相比,状态转移方程只是交换了for循环的顺序,最终结果均是对于每一个面额,考虑从所有种零钱直接得到。

#include <stdio.h>
#define min(a,b) (a<b?a:b)
int c, n, m[11], w, count[64001];
int main(){
    scanf("%d", &c);
    for (int t = 1; t <= c; ++t){
        scanf("%d", &n);
        for (int i = 1; i <= n; ++i)
            scanf("%d", &m[i]);
        scanf("%d", &w);
        for (int i = 1; i <= w; ++i)
            count[i] = 0x3fffffff;
        for (int i = 1; i <= n; ++i)//背包种类
        for (int j = m[i]; j <= w; ++j)//最大价值
            count[j] = min(count[j], count[j - m[i]] + 1);
        printf("Case #%d\n%d\n", t, count[w]);
    }
    return 0;
}

用于正确性测试的两组case:

2
3
1 4 6
8 
6
1 4 5 7 16 20
4758


(四)补充

动态规划算法求解的问题具有最优子结构性质(一个问题的最优解包含其子问题的最优解)

无后效性(将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态).

 

posted @ 2018-01-22 15:05  proscientist  阅读(309)  评论(0)    收藏  举报