zoukankan      html  css  js  c++  java
  • POJ1742 Coins(男人八题之一)

    前言

    大名鼎鼎的男人八题,终于见识了...

    题面

    http://poj.org/problem?id=1742

    分析

    § 1 多重背包

    这很显然是一个完全背包问题,考虑转移方程:

    DP[i][j]表示用前i种硬币能否取到金额j,ture表示可以,false表示不行。

    则有

    DP[i][j] = DP[i - 1][j] | DP[i - 1][j - k * Ai], 0 ≤ k ≤ Ci, j - k * Ai ≥ 0

    这是一个O(N3)的算法,考虑到数据范围1 ≤ N ≤ 100, M ≤ 100000, 1 ≤ Ci ≤ 1000,显然会超时。

    § 2 优化

     考虑上面的转移方程,每个方程只记录了可行解的存在与否;而事实上,当前第i种硬币的剩余数也是一个状态,而此前的方程关于这个状态是靠k来枚举。

    我们可以考虑一下完全背包和多重背包的异处,正是多了一个物品数量的限制,才使我们的枚举增加了一个维度,导致时间复杂度增加;而我们同时还要记录当前状态可达到的最大价值(普通多重背包),因而枚举选的物品数量的循环时必不可少的。但是,在这个问题中,我们只需要判断可行解的存在性。因此,我们可以用DP[i][j]来记录用前i种硬币,在取到金额j的情况下,第i种硬币剩余的最大数量。(不能用-1表示)

    则有

    DP[i][j] =

    • C[i], DP[i - 1][j] ≥ 0(如果前(i-1)种硬币已经足以凑出这个金额j,那么就根本用不着第种硬币,因此全部剩下,也就是Ci
    • -1, DP[i][j - A[i]] ≤ 0 || j < A[i](若前面凑更小的面额时已经用尽第i种硬币,或者当前硬币的面额太大了,那么就凑不出来,即-1)
    • DP[i][j - A[i]] - 1

    注意这些条件时依次判断的。

    (如果以上看了仍然不懂的同学,可以去看这个dalao写的详细推理过程 http://www.hankcs.com/program/cpp/poj-1742-coins.html

    因而,时间复杂度就被降到了O(N2)。

    还有很重要的一点,之间建N * M的数组是会超空间的,因此,要使用滚动数组。

    § 3 总结

    事实上,这题降维就是考的只判断解是否可行的条件。一般来说,这种思路很难想到,很少人会去想写这样一个多重背包的方程,但是,正是这个问题独特的性质,使得它可行。

    而且这个问题还有一种用单调队列的写法,目前还没有弄懂,不过也是多重背包的一大利器。

    § 4 参考代码

    // POJ1742
    // Coins
    // LouTiancheng@POJ
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    const int MAXN = 105;
    const int MAXM = 100005;
    
    int N, M, DP[2][MAXM] = {0}, A[MAXN], C[MAXN], i, j;
    
    void solve();
    
    int main() {
        while (scanf("%d%d", &N, &M) == 2) {
            if (N == 0 || M == 0) break;
            solve();
        }
        return 0;
    }
    
    void solve() {
        for (i = 0; i < N; i++) scanf("%d", &A[i]);
        for (i = 0; i < N; i++) scanf("%d", &C[i]);
        int *prv = DP[0], *nxt = DP[1];
        memset(prv + 1, -1, sizeof(int) * M);
        prv[0] = 0;
        for (i = 0; i < N; i++) {
            for (j = 0; j <= M; j++)
                if (prv[j] >= 0) nxt[j] = C[i];
                else if (j < A[i] || nxt[j - A[i]] <= 0) nxt[j] = -1;
                else nxt[j] = nxt[j - A[i]] - 1;
            std::swap(prv, nxt);
        }
        int ans = 0;
        for (i = 1; i <= M; i++)
            if (prv[i] >= 0) ans++;
        printf("%d
    ", ans);
    }

    另外,有问题的童鞋欢迎提问~

    谢谢大家!

  • 相关阅读:
    33.数组声明方式(var构造函数) 、检测数组类型、数组的属性(封装好的就一个length)、数组的方法
    31.this指向(写出调用链,找最近对象) this的默认绑定 隐式绑定 显示绑定(call(绑定对象) apply(绑定对象) 当括号内没放绑定对象的时候恢复默认绑定) bind
    31.
    30.函数作用域链 (GO AO 也叫词法作用域链)、 调用栈、调用栈涉及this绑定
    29.包装类(构造函数) 包装类作用及调用栈
    916. Word Subsets
    246. Strobogrammatic Number
    445. Add Two Numbers II
    2. Add Two Numbers
    341. Flatten Nested List Iterator
  • 原文地址:https://www.cnblogs.com/CaptainSlow/p/9245369.html
Copyright © 2011-2022 走看看