HJ16-动态规划
题目描述
王强今天很开心,公司发给N元的年终奖。王强决定把年终奖用于购物,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:
主件 附件
电脑 打印机,扫描仪
书柜 图书
书桌 台灯,文具
工作椅 无
如果要买归类为附件的物品,必须先买该附件所属的主件。每个主件可以有 0 个、 1 个或 2 个附件。附件不再有从属于自己的附件。王强想买的东西很多,为了不超出预算,他把每件物品规定了一个重要度,分为 5 等:用整数 1 ~ 5 表示,第 5 等最重要。他还从因特网上查到了每件物品的价格(都是 10 元的整数倍)。他希望在不超过 N 元(可以等于 N 元)的前提下,使每件物品的价格与重要度的乘积的总和最大。
设第 j 件物品的价格为 v[j] ,重要度为 w[j] ,共选中了 k 件物品,编号依次为 j 1 , j 2 ,……, j k ,则所求的总和为:
v[j 1 ]w[j 1 ]+v[j 2 ]w[j 2 ]+ … +v[j k ]*w[j k ] 。(其中 * 为乘号)
请你帮助王强设计一个满足要求的购物单。
输入描述:
输入的第 1 行,为两个正整数,用一个空格隔开:N m
(其中 N ( <32000 )表示总钱数, m ( <60 )为希望购买物品的个数。)
从第 2 行到第 m+1 行,第 j 行给出了编号为 j-1 的物品的基本数据,每行有 3 个非负整数 v p q
(其中 v 表示该物品的价格( v<10000 ), p 表示该物品的重要度( 1 ~ 5 ), q 表示该物品是主件还是附件。如果 q=0 ,表示该物品为主件,如果 q>0 ,表示该物品为附件, q 是所属主件的编号)
输出描述:
输出文件只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值( <200000 )。
示例1
输入
1000 5
800 2 0
400 5 1
300 5 1
400 3 0
500 2 0
输出
2200
相当困难的一题,因为之前没有弄懂动态规划还得重新学,解题过程还遇到bug。
动态规划知识
从算法图解开始,了解什么是动态规划,01背包问题和完全背包问题的解法:https://blog.csdn.net/bohu83/article/details/91453227
对背包问题的空间优化,以及,为什么在第二个循环体中01背包要逆序遍历,完全背包正序遍历:https://zhuanlan.zhihu.com/p/93857890
背包九讲,讲述更多灵活变化的背包问题解法,如何把它们化为01背包、完全背包、多重背包:https://blog.csdn.net/yandaoqiusheng/article/details/84782655
经过以上知识的学习,我们可以判断这题属于,有依赖的背包问题,但由于数据量小,可以化简为分组背包问题。
将附件归属到主件,对于主件i,有五种情况:都不取、只取主件、只取主件和附件1,只取主件和附件2,取主件附件1和附件2
状态转移方程为:f[i][j]=max(f[i-1][j]、主件+f[i-1][j-主件]、主件+附件1+f[i-1][j-主件-附件1]、主件+附件2+f[i-1][j-主件-附件2]、主件+附件1+附件2++f[i-1][j-主件-附件1-附件2])
代码实现
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// n为总钱数,m为总件数
int n = scanner.nextInt();
int m = scanner.nextInt();
Good[][] goods = new Good[m][3];
//goods[i][0]为主件,goods[i][1]和goods[i][1]为附件
for (int i = 0; i < m; i++) {
int v = scanner.nextInt();
int p = scanner.nextInt();
int q = scanner.nextInt();
Good good = new Good(v, v * p);
if (q == 0) {
goods[i][0] = good;
} else if (goods[q - 1][1] == null) {
goods[q - 1][1] = good;
} else {
goods[q - 1][2] = good;
}
}
// dp数组,n为价格,f[n]为总vp
int[] f = new int[n + 1];
for (int i = 0; i < m; i++) {
for (int j = n; goods[i][0] != null && j >= 0; j--) {
// 在f[j]、主件、主件+附件1、主件+附件2、主件+附件1+附件2这五种情况中找到最大vp
int max = f[j];
Good[] curr = goods[i];
// 若钱够买主件且买后总vp更大,更新max
if (j >= curr[0].v && curr[0].vp + f[j - curr[0].v] > max) {
max = curr[0].vp + f[j - curr[0].v];
}
// 同理
if (curr[1] != null && j >= curr[0].v + curr[1].v &&
curr[0].vp + curr[1].vp + f[j - curr[0].v - curr[1].v] > max) {
max = curr[0].vp + curr[1].vp + f[j - curr[0].v - curr[1].v];
}
if (curr[2] != null && j >= curr[0].v + curr[2].v &&
curr[0].vp + curr[2].vp + f[j - curr[0].v - curr[2].v] > max) {
max = curr[0].vp + curr[2].vp + f[j - curr[0].v - curr[2].v];
}
if (curr[1] != null && curr[2] != null && j >= curr[0].v + curr[1].v + curr[2].v &&
curr[0].vp + curr[1].vp + curr[2].vp + f[j - curr[0].v - curr[1].v - curr[2].v] > max) {
max = curr[0].vp + curr[1].vp + curr[2].vp + f[j - curr[0].v - curr[1].v - curr[2].v];
}
f[j] = max;
}
System.out.println(Arrays.toString(f));
}
System.out.println(f[n]);
}
}
class Good {
int v;
int vp;
public Good(int v, int vp) {
this.v = v;
this.vp = vp;
}
}