目录
一、定义
完全背包可以看作是01背包演变过来的,其定义如下:
我们有一个背包容量是V
,给定我们N
种物品,每个物品有其对应的价值wi
和占用体积vi
( 1 ≤ i ≤ N 1\leq i \leq N 1≤i≤N),并且每种物品都有无限个,要求我们在不超过V
的情况下,选取物品,使得总价值最大化。
它和01背包唯一的区别就是在物品的选择次数上。完全背包中,每种物品是由无限个的,你可以选择任意多个物品,当然选0个也是可以的,而后者只能选或者不选。
二、模板题
完全背包问题描述
有一个容量为 ( V = 10 ) 的背包,现有 ( N = 4 ) 种不同的物品,每种物品的数量是无限的。每种物品的重量和价值如下:
- 物品 1:重量为 2,价值为 3
- 物品 2:重量为 3,价值为 4
- 物品 3:重量为 4,价值为 5
- 物品 4:重量为 5,价值为 6
你可以选择任意数量的每种物品,但总重量不能超过背包的容量 ( V = 10 )。你的目标是在不超过背包容量的前提下,选取物品,使得背包内物品的总价值最大化。
请编写程序,求出背包能获得的最大价值。
输入:
- 背包的总容量 ( V = 10 )
- 物品的重量数组:
weights = {2, 3, 4, 5}
- 物品的价值数组:
values = {3, 4, 5, 6}
输出:
- 背包能获得的最大价值:
12
示例:
输入:
背包容量: 10
物品重量: [2, 3, 4, 5]
物品价值: [3, 4, 5, 6]
返回:
背包能获得的最大价值为: 12
解题思路:
java
class CompleteKnapsack {
/**
* 解决完全背包问题
* @param V 背包的总容量
* @param weights 物品的容量数组
* @param values 物品的价值数组
* @return 背包能获得的最大价值
*/
//这个函数用来返回最大价值,在不超过V的情况下。
public static int solve(int V, int[] weights, int[] values) {
}
}
1. 状态表示:
java
//dp[v]表示,背包容量为v时,获取的最大价值
int[] dp=new int[V+1];
2. 状态转移方程
当前第i
个物品的价值是vlues[i]
,体积是weigths[i]
,如何求dp[i]呢?
dp[v]表示:背包容量为v时,获取的最大价值。因此我们需要将当前的容量v
和第i个物品的体积weigths[i]进行比较:
java
//在背包容量v不小于第i个物品的体积的情况下,尝试把第i个物品放进包里
if (weights[i] <=v) {
dp[v] = Math.max(dp[v], dp[v - weights[i]] + values[i]);
}
//weights[i]>v不做任何处理,因为第i个物品体积太大了,即使容量为v的背包没有装任何东西,也装不下这个物品
3.初始化
根据状态表示,我们只需要确保dp[0]为正确的值即可。dp[0]表示背包容量为0时,获取的最大价值。因为根本装不了东西自然dp[0]=0
4. 填表顺序
因为完全背包是可以无限选择多个同种物品的,只要不超过背包容量,所以我们先从1到V枚举所有可能的背包容量,然后再内层循环从1到N枚举所有的物品,这样才能确保有多次选择同一个物品的可能:
java
int N=weights.length; // 物品的数量
for (int v = 0; v <=V; v++)
for (int i = 0; i <N; i++) {// 遍历所有的物品
//在背包容量v不小于第i个物品的体积的情况下,尝试把第i个物品放进包里
if (weights[i] <=v)
dp[v] = Math.max(dp[v], dp[v - weights[i]] + values[i]);
}
5. 确定返回值
即,dp[V]( 容量为V的背包,获取的最大价值)
代码模板(Java)
java
class CompleteKnapsack {
/**
* 解决完全背包问题
* @param V 背包的总容量
* @param weights 物品的容量数组
* @param values 物品的价值数组
* @return 背包能获得的最大价值
*/
public static int solve(int V, int[] weights, int[] values) {
int N = weights.length; // 物品的数量
int[] dp = new int[V + 1]; // dp[v]表示容量为i的背包所能装入的最大价值
// 遍历所有可能的背包容量
for (int v = 0; v <=V; v++) {
// 遍历所有的物品
for (int i = 0; i <N; i++) {
//在背包容量v不小于第i个物品的体积的情况下,尝试把第i个物品放进包里
if (weights[i] <=v) {
// 比较不放入该物品和放入该物品的情况,取较大值
// dp[v]:不放入当前物品时的最大价值
// dp[v - weights[i]] + values[i]:放入当前物品后的最大价值
dp[v] = Math.max(dp[v], dp[v - weights[i]] + values[i]);
}
}
}
// 返回背包容量为V时能获得的最大价值
return dp[V];
}
三、实战题
1、题目
2、解题思路
先入为主,大家想到的肯定是用完全背包去做,当然也的确如此,不过需要一点转化。首先这个电影是可以选择重复上映多次的,这个一定要清楚,题目中并没有明确说明。这满足完全背包无限选取同一个物品的特征。 (第i
部电影的放映时长就是i
物品的体积,第i
部电影的利润就是i
物品的价值)
另外,题目中每场电影上映完至少要等待K
分钟才可以上映下一场电影,这就比较的难受。有没有什么办法可以避开它呢?
当然有,而且思路并不难:直接把时间间隔K
视为每一个物品的体积的一部分(每部电影时长增加K
)。值得注意的是,最后上映的一部电影是不需要增加休息时间的间隔K
的,因为后面已经没有电影要放了,所以我们要对最后一个位置做特殊处理:
也不用把它想得太难,直接扩容背包的最大体积即可,也就是电影院的放映总时间增加 K
即可。
通过上面的转化,题目就变成了经典的完全背包问题了。
-
背包的容量: 电影的总放映时间+
K
-
物品的体积: 第i部电影的放映时长+
K
-
物品的价值: 第i部电影的利润
-
物品的数量: 可放映的电影的数量
3、AC_Code_Java
java
import java.util.*;
// 1:无需package
// 2: 类名必须Main, 不可修改
public class Main {
//转化成完全背包(体积有限,但是同一个物品可以无限拿多个)
//所以我们直接对每个电影的时间都加上一个间隔
//但是由于最后一个电影不需要间隔,所以可以在最大时间M后面+k
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
int m=sc.nextInt();//开店时间
int n=sc.nextInt();//电影的多少
int[] t=new int[n+1];//每部电影的放映时长
int[] p=new int[n+1];//每部电影的利润
for(int i=1;i<=n;i++){
t[i]=sc.nextInt();
p[i]=sc.nextInt();
}
int k=sc.nextInt();//最小间隔
//把物品的体积统一
for(int i=1;i<=n;i++)t[i]+=k;
//电影院的总放映时长加上时间间隔,处理最后一个特殊情况
m+=k;
//dp[i]电影院总放映时长为i时,最大利润
int[] dp=new int[m+1];
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(i>=t[j])dp[i]=Math.max(dp[i],dp[i-t[j]]+p[j]);
}
}
System.out.print(dp[m]);
}
}