动态规划-购物单

162 阅读3分钟

描述

题目来自这里:www.nowcoder.com/practice/f9…

王强决定把年终奖用于购物,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:

主件附件
电脑打印机,扫描仪
书柜图书
书桌台灯,文具
工作椅

如果要买归类为附件的物品,必须先买该附件所属的主件,且每件物品只能购买一次。

每个主件可以有 0 个、 1 个或 2 个附件。附件不再有从属于自己的附件。

王强查到了每件物品的价格(都是 10 元的整数倍),而他只有 N 元的预算。除此之外,他给每件物品规定了一个重要度,用整数 1  ~ 5 表示。他希望在花费不超过 N 元的前提下,使自己的满意度达到最大。

满意度是指所购买的每件物品的价格与重要度的乘积的总和,假设设第ii件物品的价格为v[i]v[i],重要度为w[i]w[i],共选中了kk件物品,编号依次为j_1,j_2,...,j_kj1​,j2​,...,jk​,则满意度为:v[j_1]*w[j_1]+v[j_2]*w[j_2]+ … +v[j_k]*w[j_k]v[j1​]∗w[j1​]+v[j2​]∗w[j2​]+…+v[jk​]∗w[jk​]。(其中 * 为乘号)

请你帮助王强计算可获得的最大的满意度。

解题

我是用的常用解题思路来解的:

for 状态1 in 状态1的所有取值:    
    for 状态2 in 状态2的所有取值:  
        for ...  dp[状态1][状态2][...] = 择优(选择1,选择2...)

不过在这之前先找到了数组的最大公因数,让各数据除以公因数,最后计算的结果乘以公因数就可以了,下面是具体代码:

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int totalMoney = sc.nextInt();
        int totalNum = sc.nextInt();
        int[] moneys = new int[totalNum];
        int[] importants = new int[totalNum];
        int[] parents = new int[totalNum];
        for(int i=0;i<totalNum;i++){
            moneys[i] = sc.nextInt();
            importants[i] = sc.nextInt();
            parents[i] = sc.nextInt();
        }
        //求出最大公因数
        int gys = maxGys(moneys);
        totalMoney = totalMoney / gys;
        for(int i=0;i<moneys.length;i++){
            moneys[i] = moneys[i] / gys;
        }

        Node[][] dp = new Node[totalNum+1][totalMoney+1];
        
        for(int i = 0;i<=totalNum;i++){
            for(int m = 0;m <= totalMoney;m++){
                if(i == 0 || m == 0){
                    dp[i][m] = new Node();
                    continue;
                }
                dp[i][m] = new Node();
                if(m - moneys[i-1] < 0){
                    dp[i][m] = dp[i-1][m];
                }else{
                    if(parents[i-1] == 0){//父节点
                        int lastV = dp[i-1][m].value;
                        int andV = dp[i-1][m-moneys[i-1]].value + (moneys[i-1] * importants[i-1]);
                        if(lastV > andV){
                            dp[i][m] = dp[i-1][m];
                        }else{
                            dp[i][m].value = andV;
                            //把父节点记录进去
                            dp[i][m].parents.add(i);
                            dp[i][m].parents.addAll(dp[i-1][m-moneys[i-1]].parents);
                        }
                    }else{//子节点
                        if(dp[i-1][m-moneys[i-1]].parents.contains(parents[i-1])){//里面有该子节点的父节点
                            int lastV = dp[i-1][m].value;
                            int andV = dp[i-1][m-moneys[i-1]].value + (moneys[i-1] * importants[i-1]);
                            if(lastV > andV){
                                dp[i][m] = dp[i-1][m];
                            }else{
                                dp[i][m].value = andV;
                                //把父节点记录进去
                                dp[i][m].parents.addAll(dp[i-1][m-moneys[i-1]].parents);
                            }
                        }else{//无,则继续判断
                            if(m - moneys[i-1] - moneys[parents[i-1]-1] < 0){//不够添加父节点
                                dp[i][m] = dp[i-1][m];
                            }else{//够添加
                                int lastV = dp[i-1][m].value;
                                if(dp[i-1][m-moneys[i-1]-moneys[parents[i-1]-1]].parents.contains(parents[i-1])){//已经含有该父节点了
                                    int andV = dp[i-1][m-moneys[i-1]-moneys[parents[i-1]-1]].value + (moneys[i-1] * importants[i-1]);
                                    if(lastV > andV){
                                        dp[i][m] = dp[i-1][m];
                                    }else{
                                        dp[i][m].value = andV;
                                        //把父节点记录进去
                                        dp[i][m].parents.addAll(dp[i-1][m-moneys[i-1]-moneys[parents[i-1]-1]].parents);
                                    }
                                }else{
                                    int andV = dp[i-1][m-moneys[i-1]-moneys[parents[i-1]-1]].value + (moneys[i-1] * importants[i-1]) + moneys[parents[i-1]-1] * importants[parents[i-1]-1];
                                    if(lastV > andV){
                                        dp[i][m] = dp[i-1][m];
                                    }else{
                                        dp[i][m].value = andV;
                                        //把父节点记录进去
                                        dp[i][m].parents.add(parents[i-1]);
                                        dp[i][m].parents.addAll(dp[i-1][m-moneys[i-1]-moneys[parents[i-1]-1]].parents);
                                    }
                                }
                            }
                            
                        }
                    }
                }
            }
        }

        System.out.println(dp[totalNum][totalMoney].value * gys);
    }
    /**
     * 满意度 类
     */
    static class Node{
        //满意度
        public int value;
        //含有的父节点
        public List<Integer> parents = new ArrayList<>();
    }

    /**
     * 最大公因数
     */
    private static int maxGys(int[] moneys){
        if(moneys.length == 1){
            return moneys[0];
        }
        int temp = moneys[0];
        for(int i = 1;i<moneys.length;i++){
            temp = gcd(temp,moneys[i]);
        }
        return temp;
    }

    //迭代计算两个最大公因数的方法
	private static int gcd(int m,int n){  
        if(n == 0){
	        return m; 
	    }
	    int r = m%n;
	    return gcd(n,r);
	}
    
}