背包问题
1. 问题描述:
有n个重量和价值分别为wi,vi的物品,从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值
1≤n≤100
1≤wi,vi≤100
1≤W≤10000
输入:
第一行是n
第二行到是物品的重量
第三行是物品对应的价值
而且物品的数量是无限的,可以无限拿取
2. ①与零一背包不同的是,零一背包中的物品是不可以重复拿取的,只可以拿取当前物品或者不拿取当前物品,不可以拿取多个,完全背包的物品是可以任意拿取多个的来构成不超过背包容量并且构成的总价值是最大的
② 首先我们是可以使用试探的方式来拿取物品的,对于当前的物品我们可以不拿取,拿取一个,拿取两个...直到不能够拿取当前物品了,这种试探的思维我们是可以使用深度优先搜索来进行解决的,但是时间复杂度可能有点高,对于这种经典的求解最大值的问题我们是可以使用动态规划来进行解决的
③ 动态规划的核心是找到dp公式或者状态转移的方程,理解清楚中间的过程是怎么样进行变化的,因为动态规划总是要利用到之前历史上的最佳方案,所以dp数组里面存储的肯定是历史上存储的最佳方案,一开始的时候我们是可以借助excel表格来帮助我们理解dp数组是怎样生成的
④ 我们可以这样想:当前我的背包容量有多少,而且我可以选择的物品的范围是什么,那么这两个变量就可以确定一个值了,我们可以计算出当前背包容量对于当前可以选择的物品范围构成的最大价值
所以借助excel表格来帮助我们理解dp表的生成,其中以物品的质量为{7, 4, 3, 2},价值为{9, 5, 3, 1}为例,下面是生成的过程:
横坐标是目前的背包容量,纵坐标是当前可以选择的物品的范围,那么excel表格填出来的结果如下:
⑤ 采用的策略是:对于当前的物品我们是可以选择不拿取的,因为可以重复拿取那么我们是可以拿取当前物品的1,2,3...k倍的,并且k * w[i] <= W
k为当前的倍数,w[i]为当前物品的质量,W为剩余物品的质量,拿取当前物品的k倍之后那么剩余的容量拿取的最大价值我们应该在上一行中对应的剩余物品质量的列去找,把不拿取当前物品,拿取1,2,3...k倍中计算出的价值进行比较在这几个中求出最大值,这个最大值就是当前单元格的对应的dp数组的值,那么当循环结束之后dp数组的最后一个元素那么肯定就是我们需要求解的背包容量为w拿取物品的最大价值,直接返回就可以了
⑥ 此外在生成dp表的过程中更需要先对dp数组的第一行与第一列进行初始化,对于第一行判断我们可以拿取当前物品多少个,那么就可以加上这个物品的多少倍的价值,对于dp数组的第一列我们都设置为零,因为当前的背包容量为零我们不可以拿取任何的物品,这个时候可以不对dp数组的第一列进行初始化,因为数组元素的默认值为零
⑦ 其中我感觉背包问题最重要的是:把横坐标确定为背包容量,纵坐标确定为当前可以选择的物品的范围那么不管是何种背包问题我都可以借助excel表格来进行dp数组生成过程中单元格数字的填写,理解好单元格的意义并且单元格的上一行的值代表的是什么意思,在填写excel表格的时候那么我们对于背包问题的理解就比较深刻了,可以发现在填写dp表格的过程中行数在递增的过程中那么其中可以选择的物品的范围也在变化,其实这实际上也是在当前可以选择的物品范围内组合选择最佳方案的过程,等到填完表格中的一部分理解清楚其中的过程那么写代码就不是很困难了,所以excel表格非常重要
3. 具体的代码如下:
import java.util.scanner;
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int n = sc.nextint();
int w[] = new int[n];
int v[] = new int[n];
for(int i = 0; i < n; i++){
w[i] = sc.nextInt();
}
for(int i = 0; i < n; i++){
v[i] = sc.nextInt();
}
int W = sc.nextInt();
int res = solution(w, v, n, W);
System.out.println(res);
sc.close();
}
private static int solution(int[] w, int[] v, int n, int W){
int dp[][] = new int[n][W + 1];
for(int i = 0; i <= W; i++){
dp[0][i] = i / w[0] * v[0];
}
for(int i = 0; i < n; i++){
dp[i][0] = 0;
}
int max = 0;
for(int i = 1; i < n; i++){
for(int j = 1; j <= W; j++){
for(int k = 0; k * w[i] <= j; k++){
int t = k * v[i] + dp[i - 1][j - k * w[i]];
if(max < t){
max = t;
}
}
dp[i][j] = max;
//特别要注意max要重置为零,否则dp数组里面的值是错误的
max = 0;
}
}
return dp[n - 1][W];
}
}
测试数据:
4
7 4 3 2
9 5 3 1
10
应该输出12
其中在测试的过程中把最终dp数组的值输出来来看一下dp数组里面记录的值是否正确,假如不正确根据其中的数字进行判断哪里出现了错误来进行程序的调整
相关阅读
题记:最近果真是诸事不顺,没钱的日子里手机又丢了,还是比较伤心滴,更伤心地是我那里面有各种日期提醒啊,无限郁闷中。。。最最伤心的是
面对他人的提问,一定要直接给出答案才能帮到他吗?事实证明“授人以鱼不如授人以渔”,善用提问才能更好地帮助对方找到答案。今天我想
钱币组合问题(一):(每种硬币不限次数)假设我们有8种不同面值的硬币{1,2,5,10,20,50,100,200},用这些硬币组合够成一个给定的数值n。例如n=200,那么
今天偶然有人问到端口的范围0-65535;百度了一下说是这个范围。仔细想了一下,这个回答太笼统,0是不是可以做端口?65535是不是包含
社会竞争很残酷、面试其实就是一场表演,企业永远喜欢可以随机应变、聪明的求职者。而不喜欢看似老实、实则笨拙不懂变通的求职者。