赞
踩
完全背包问题(UKP, unbounded knapsack problem):每种物品都有无限个可用
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] + ".");
}
}
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。