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;
    }
}
posted @ 2021-01-26 14:17  tanjr  阅读(199)  评论(0编辑  收藏  举报