题目主要信息
1、从m个物品中选出若干个,其总价低于N
2、物品分为主件与附件,如果要买为附件的物品,必须先买该附件所属的主件
3、使得每件物品的价格与重要度的乘积的总和最大
方法一:动态规划
具体方法
本题很明显是一道01背包问题,对于这种问题,我们一般采用动态规划的方法来进行解决。我们定义动规数组f[i][j]来表示前i件物品,容量为j时的最大价值,则
在本题中进行了一项变动,即物品分为主件和附件,考虑到一个主件最多可以购买两个附件,那我们可以细化分析,将是否购买该物品,细化为是否购买该物品,以及是否购买该物品的附件,即5种情况,不购买该物品,购买该物品,购买该物品及附件1,购买该物品及附件2,购买该物品及附件1及附件2,f[i][j]取这五种情况的最大值,这五种情况分别对应于
,
,
,
,
,
其中和是该物品的附件。
下面用一个示例来说明,各个颜色表示最优方案,其中白块表示初始化区域,绿块表示不取物品最大,红块表示只取物品主件,紫块表示取物品主件和附件1,黄块表示取物品主件和附件2,蓝块表示取物品主件和两个附件
Java代码
import java.util.Scanner;
public class Main {
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int m = sc.nextInt();
Goods[] goods = new Goods[m];
for(int i = 0; i < m; i++){
goods[i] = new Goods();
}
for(int i = 0; i < m; i++){
int v = sc.nextInt();
int p = sc.nextInt();
int q = sc.nextInt();
goods[i].v = v;
goods[i].p = p * v; // 直接用p*v,方便后面计算
if(q==0){
goods[i].main = true;
}else if(goods[q-1].a1 == -1){
goods[q-1].a1 = i;
}else{
goods[q-1].a2 = i;
}
}
int[][] dp = new int[m+1][N+1];
for(int i = 1; i <= m; i++){
for(int j = 0; j <= N; j++){
dp[i][j] = dp[i-1][j];
if(!goods[i-1].main){
continue;
}
if(j>=goods[i-1].v){
dp[i][j] = Math.max(dp[i][j], dp[i-1][j-goods[i-1].v] + goods[i-1].p);
}
if(goods[i-1].a1 != -1 && j >= goods[i-1].v + goods[goods[i-1].a1].v){
dp[i][j] = Math.max(dp[i][j], dp[i-1][j-goods[i-1].v - goods[goods[i-1].a1].v] + goods[i-1].p + goods[goods[i-1].a1].p);
}
if(goods[i-1].a2 != -1 && j >= goods[i-1].v + goods[goods[i-1].a2].v){
dp[i][j] = Math.max(dp[i][j], dp[i-1][j-goods[i-1].v - goods[goods[i-1].a2].v] + goods[i-1].p + goods[goods[i-1].a2].p);
}
if(goods[i-1].a1 != -1 && goods[i-1].a2 != -1 && j >= goods[i-1].v + goods[goods[i-1].a1].v + goods[goods[i-1].a2].v){
dp[i][j] = Math.max(dp[i][j], dp[i-1][j-goods[i-1].v - goods[goods[i-1].a1].v - goods[goods[i-1].a2].v] + goods[i-1].p + goods[goods[i-1].a1].p + goods[goods[i-1].a2].p);
}
}
}
System.out.println(dp[m][N]);
}
}
class Goods {
int v;
int p;
boolean main = false;
int a1 = -1; //定义附件1的编号
int a2 = -1; //定义附件2的编号
}
复杂度分析
- 时间复杂度:,双层循环
- 空间复杂度:,需要一个二维额外数组
方法二:动态规划(空间压缩)
具体方法
基本与思路一一样,但是在本题中,由于我们是否取第i个商品时的数组只与是否取第i-1个商品时的数组相关,所以可以进行空间压缩,将时间复杂度压缩到,但需注意逆循环,防止前面修改被后面应用。
Java代码
import java.util.Scanner;
public class Main {
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int m = sc.nextInt();
Goods[] goods = new Goods[m];
for(int i = 0; i < m; i++){
goods[i] = new Goods();
}
for(int i = 0; i < m; i++){
int v = sc.nextInt();
int p = sc.nextInt();
int q = sc.nextInt();
goods[i].v = v;
goods[i].p = p * v; // 直接用p*v,方便后面计算
if(q==0){
goods[i].main = true;
}else if(goods[q-1].a1 == -1){
goods[q-1].a1 = i;
}else{
goods[q-1].a2 = i;
}
}
int[] dp = new int[N+1];
for(int i = 1; i <= m; i++){
for(int j = N; j >= 0; j--){
if(!goods[i-1].main){
continue;
}
if(j>=goods[i-1].v){
dp[j] = Math.max(dp[j], dp[j-goods[i-1].v] + goods[i-1].p);
}
if(goods[i-1].a1 != -1 && j >= goods[i-1].v + goods[goods[i-1].a1].v){
dp[j] = Math.max(dp[j], dp[j-goods[i-1].v - goods[goods[i-1].a1].v] + goods[i-1].p + goods[goods[i-1].a1].p);
}
if(goods[i-1].a2 != -1 && j >= goods[i-1].v + goods[goods[i-1].a2].v){
dp[j] = Math.max(dp[j], dp[j-goods[i-1].v - goods[goods[i-1].a2].v] + goods[i-1].p + goods[goods[i-1].a2].p);
}
if(goods[i-1].a1 != -1 && goods[i-1].a2 != -1 && j >= goods[i-1].v + goods[goods[i-1].a1].v + goods[goods[i-1].a2].v){
dp[j] = Math.max(dp[j], dp[j-goods[i-1].v - goods[goods[i-1].a1].v - goods[goods[i-1].a2].v] + goods[i-1].p + goods[goods[i-1].a1].p + goods[goods[i-1].a2].p);
}
}
}
System.out.println(dp[N]);
}
}
class Goods {
int v;
int p;
boolean main = false;
int a1 = -1; //定义附件1的编号
int a2 = -1; //定义附件2的编号
}
复杂度分析
- 时间复杂度:,双层循环
- 空间复杂度:,只需要一维数组保存状态