动态规划入门之完全背包

完全背包问题(UKP, unbounded knapsack problem):每种物品都有无限个可用

 

最简单思路就是把完全背包拆分成01背包,就是把01背包中状态转移方程dp[i][j]=dp[i-1][j-w[i]]+v[i]相应的做点修改,也就是说01背包只考虑放与不放进去两种情况,而完全背包要考虑 放0、放1、···、放j/w[i] 的情况。

            for (int i = 1; i <= n; i++) {
                for (int j = 1; j <= m; j++) {
                    dp[i][j] = dp[i - 1][j];           //不放进去

                          for(int k=1;k<=j/w[i];j++)
                          dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - k*w[i] ] + k*v[i] );         //放进去k个

                }
            }

把第i 种物品换成M/w[i] 个0-1 背包问题中的物品,则得到了物品数为Σ M/w[i]的0-1 背包问题。时间复杂度是O(NM*Σ M/w[i])

 

但是还有一种状态转移方程:dp[i][j]=max{dp[i-1][j],dp[i][j-w[i] ]+v[i]}

这种方法也是分两种情况:第i个放进去与不放进去,但是放进去不能仅仅代表只放进去一个了,dp[i][j-1]表示j-1的空间放前i种物品,不限第i种物品有多少个,可以是0个、

1个···直到第i种物品不能再多的情况,所以dp[i][j-1]+v[i]代表至少放了一个第i种物品,当然它的前提是能放进去(j>=w[i]),所以dp[i][j]=max{dp[i-1][j],dp[i][j-w[i] ]+v[i]}已经涵盖了一个都不放与至少放一个第i种物品的情况了,也就是组成了所有可能的情况,没有遗漏。

 

接下来是例题(poj1384):

描述
给你一个储钱罐(piggy bank),往里面存硬币。存入的过程是不可逆的,要想把钱拿出来只能摔
碎储钱罐。因此,你想知道里面是否有足够多的钱,把它摔碎是值得的。
你可以通过储钱罐的重量来推测里面至少有多少钱。已知储钱罐空的时候的重量和装了硬币后
的重量,还有每种硬币的重量和面值,每种硬币的数量不限。求在最坏情况下,储钱罐里最少有多
少钱。
输入
第1 行包含一个整数T,表示有T 组测试用例。每组测试用例,第一行是两个整数E 和F,分
别表示空储钱罐的重量和装了硬币后的重量,以克(gram) 为单位,储钱罐的重量不会超过10kg,即

1 <= E <= F <= 10000。第二行是一个整数N(1 <= N <= 500),表示硬币的种类数目。接下来是N 行,
每行包含两个整数v 和w(1 <= v <= 50000; 1 <= w <= 10000),分别表示硬币的面值和重量。
输出
每个案例打印一行。内容是”e minimum amount of money in the piggy-bank is X.”,其中X 表示
储钱罐里最少有多少钱。如果不能精确地达到给定的重量,则打印”is is impossible.”。
样例输入
3
10 110
21
1
30 50
10 110
21
1
50 30
1 6
2
10 3
20 4
样例输出
The minimum amount of money in the piggy-bank is 60.
The minimum amount of money in the piggy-bank is 100.
This is impossible.

 

java代码(MLE无法AC):

import java.util.Scanner;

public class FullKnapsack {

    static final int MAX = 9999;

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int num = scan.nextInt();
        while (num-- != 0) {
            int empty = scan.nextInt();
            int full = scan.nextInt();
            int w = full - empty;
            int n = scan.nextInt();
            int[] v = new int[n + 1];
            int[] m = new int[n + 1];
            int[][] dp = new int[n + 1][w + 1];          //dp[i][j]前i个物品凑j重量,凑得到则 < MAX,凑不到则赋MAX
            for (int i = 1; i <= n; i++) {
                v[i] = scan.nextInt();
                m[i] = scan.nextInt();

            }
            for (int j = 1; j <= w; j++) {
                dp[0][j] = MAX;                    //隐含dp[0..n][0]都为0
            }

            for (int i = 1; i <= n; i++) {
                for (int j = 1; j <= w; j++) {
                    dp[i][j] = dp[i - 1][j];          //第i个不放进去
                    if (j >= m[i]) {
                        dp[i][j] = Math.min(dp[i][j], dp[i][j - m[i]] + v[i]);        //放进去,并且1,2,3,4...取最小
                    }
              //      System.out.print(String.format("%5d",dp[i][j]));
                }
                System.out.println();
            }
            if (dp[n][w] == MAX) {
                System.out.println("This is impossible.");
            } else {
                System.out.println("The minimum amount of money in the piggy-bank is "+dp[n][w]+".");
            }

        }
    }
}

 

 

在之前的动态规划分析过程中,决策都是在可能的几种最大情况下再取最大。。

而这道题目恰相反,是取最小,而且背包必须要装满。

若是取最小,将决策中max改成min即可。

如果背包要装满,dp[0][1..w]初始值就不能为0,若是取最小,就将它赋值为极大,若取最大,则将它赋值为极小,但是注意不要越界。

判定是否装满时,就判断它是否是极大或者极小即可,但是要注意在赋值dp[i][j]的过程由于+号,可能极小值已经不再精确。

 

 下面附上能够AC的带滚动数组的java代码:

 

import java.util.Scanner;

/**
 *
 * @author Administrator
 */
public class Main {

    static final int MAX = Integer.MAX_VALUE/2;

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int num = scan.nextInt();
        while (num-- != 0) {
            int empty = scan.nextInt();
            int full = scan.nextInt();
            int w = full - empty;
            int n = scan.nextInt();
            int[] v = new int[n + 1];
            int[] m = new int[n + 1];
            int[] dp = new int[w + 1];        
            for (int i = 1; i <= n; i++) {
                v[i] = scan.nextInt();
                m[i] = scan.nextInt();

            }
            for (int j = 1; j <= w; j++) {
                dp[j] = MAX;                   
            }

            for (int i = 1; i <= n; i++) {
                for (int j = m[i]; j <= w; j++) {
                    dp[j] = Math.min(dp[j], dp[j - m[i]] + v[i]);       
                }
            }
            if (dp[w] == MAX) {
                System.out.println("This is impossible.");
            } else {
                System.out.println("The minimum amount of money in the piggy-bank is " + dp[w] + ".");
            }

        }
    }
}

你可能感兴趣的:(动态规划)