牛客HJ16:购物单思路整理与代码实现

题目链接:https://www.nowcoder.com/practice/f9c6f980eeec43ef85be20755ddbeaf4?tpId=37&tqId=21239&rp=1&ru=/exam/oj/ta&qru=/exam/oj/ta&sourceUrl=%2Fexam%2Foj%2Fta%3FtpId%3D37&difficulty=undefined&judgeStatus=undefined&tags=&title=

题目描述:

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

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

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

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

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

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

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

输入描述:

输入的第 1 行,为两个正整数N,m,用一个空格隔开:

(其中 N ( N<32000 )表示总钱数, m (m <60 )为可购买的物品的个数。)

从第 2 行到第 m+1 行,第 j 行给出了编号为 j-1 的物品的基本数据,每行有 3 个非负整数 v p q

(其中 v 表示该物品的价格( v<10000 ), p 表示该物品的重要度( 1 5 ), q 表示该物品是主件还是附件。如果 q=0 ,表示该物品为主件,如果 q>0 ,表示该物品为附件, q 是所属主件的编号)

输出描述:

 输出一个正整数,为张强可以获得的最大的满意度。

示例1

输入:
1000 5
800 2 0
400 5 1
300 5 1
400 3 0
500 2 0
输出:
2200

示例2

输入:
50 5
20 3 5
20 3 5
10 3 0
10 2 0
10 1 0
输出:
130
说明:
由第1行可知总钱数N为50以及希望购买的物品个数m为5;
第2和第3行的q为5,说明它们都是编号为5的物品的附件;
第4~6行的q都为0,说明它们都是主件,它们的编号依次为3~5;
所以物品的价格与重要度乘积的总和的最大值为10*1+20*3+20*3=130  

解题思路:

该问题为经典的0-1背包问题的变形,如果有不懂0-1背包问题的网上有很多资料,这里主要是讲解0-1背包问题的变形解法。

首先,对于0-1背包问题的状态转移方程如下:

//j表示背包容量(本题中为持有的总金额),i表示当前遍历到的物品序号,
price[i], v[i]分别表示物品的价格和价值;

//1.放得下i号物品
if(j >= [prices[i]) {
    dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - price[i]] + v[i]);
}
//2.放不下
else dp[i][j] = dp[i-1][j];

而本题中较为麻烦的一点为:存在主件和附件,不能像0-1背包一样进行从头到尾的遍历,因为附件不能脱离主件而存在。题目中给出的信息是一个主件最多两个附件,因此我们的思路可以转化为只遍历主件,遍历到主件i时,如果i有附件,那么我们就考虑当前容量j是否能容纳下1.主件,2.主件+附件1,3.主键+附件2,4.主键+附件1+附件2.再将以上四种情况中可行的情况(背包容量够和当前主件i的附件的个数)和i不放入进行比较,选择最大的作为当前的dp[i][j]值。遍历到附件则跳过,将其dp[i][j] = dp[i - 1][j];

具体实现如下:本题中,每行输入三个数字,因此我们利用三个数组对每个物品的价格,满意度,是否附件进行保存:price[],val[],itemType[]。

        int a, b, c;
        vector<int> price;
        vector<int> val;
        vector<int> itemType;
        for (int i = 0; i < m; ++i) {
            cin >> a >> b >> c;
            price.push_back(a / 10);
            val.push_back(b);
            itemType.push_back(c);
        }

然后创造dp数组,很明显的可以知道对于不选择任何物品(i = 0)以及总金额为0(j = 0),其产生的效益值都为0;因此边界条件不用额外处理。

vector<vector<int>> dp(m + 1, vector<int>(1 + N, 0));

在遍历物品时,有一个小坑,就是我们录入price[],val[],itemType[]是从0开始的,而dp数组的第0行表示的是不选择任何物品,因此我们在遍历dp时,需要将i进行减一操作,才能刚好匹配上我们的price[],val[],itemType[]数组(当然你也可以事先将0这个位置进行填充,刚好就和dp数组的下标保持一致了)。

for (int i = 1; i <= m; ++i) {
            for (int j = 1; j <= N; ++j) {
                if (itemType[i - 1] == 0) {
                    vector<int> res;
                    vector<int> slaveIndex;
                    for (int k = 0; k < m; ++k) {
                        if (itemType[k] == i) {
                            slaveIndex.push_back(k);
                        }
                    }

其中itemType[i] == 0是验证当前物品是主件我们才对其进行操作:slaveIndex数组用来保存当前主件的物品i的附件所在的下标(附件1以及附件2的下标),res数组用来存储可能产生的值:1. i不加入 2. 只加入i;3.只加入i和他的第一个附件;4.只加入i和他的第二个附件;5.加入i和他的第一和第二个附件。

因此res数组中最多有五个值;我们的dp[i][j]也就是从这五个值中选择最大的一个。

                    int tempVal = dp[i - 1][j];  //1.不加入i,第一个值
                    res.push_back(tempVal);
                    if (j >= price[i - 1]) res.push_back(dp[i - 1][j - price[i - 1]] + price[i - 1] * val[i - 1]); //如果装得下i,只装入i,第二个值
                    //只有一个附件,装入这一个附件,第三个值。这种情况下res中只有三个值。
                    if (slaveIndex.size() == 1) {
                        if (price[slaveIndex[0]] + price[i - 1] <= j) {
                            res.push_back(dp[i - 1][j - price[i - 1] - price[slaveIndex[0]]] + price[i - 1] * val[i - 1]
                                          + price[slaveIndex[0]] * val[slaveIndex[0]]);
                        }
                    }

                    //两个附件,这种情况下五个值
                    if (slaveIndex.size() == 2) {
                           //只装附件1,第三个值
                        if (price[slaveIndex[0]] + price[i - 1] <= j) {
                            res.push_back(dp[i - 1][j - price[i - 1] - price[slaveIndex[0]]] + price[i - 1] * val[i - 1]
                                          + price[slaveIndex[0]] * val[slaveIndex[0]]);
                        }
                        //只装附件2,第四个值
                        if (price[slaveIndex[1]] + price[i - 1] <= j) {
                            res.push_back(dp[i - 1][j - price[i - 1] - price[slaveIndex[1]]] + price[i - 1] * val[i - 1]
                                          + price[slaveIndex[1]] * val[slaveIndex[1]]);
                        }
                        //都装入,第五个值
                        if (price[slaveIndex[0]] + price[slaveIndex[1]] + price[i - 1] <= j) {
                            res.push_back(dp[i - 1][j - price[i - 1] - price[slaveIndex[0]] -
                                                    price[slaveIndex[1]]] + price[i - 1] * val[i - 1] + price[slaveIndex[0]] *
                                          val[slaveIndex[0]] + price[slaveIndex[1]] * val[slaveIndex[1]]);
                        }
                    }
                    /*cout<<i<<" "<<j<<": ";
                    for(auto it : res) {
                        cout<<it<<" ";
                    }*/
                    dp[i][j] = *(max_element(res.begin(), res.end())); //取最大的那个

注意每次试探装入时都有判断是否能否装得下(当前容量j是否够)。

最终完整代码如下:

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

int main() {
    int N, m;
    while (cin >> N >> m) { // 注意 while 处理多个 case
        int a, b, c;
        vector<int> price;
        vector<int> val;
        vector<int> itemType;
        for (int i = 0; i < m; ++i) {
            cin >> a >> b >> c;
            price.push_back(a / 10);
            val.push_back(b);
            itemType.push_back(c);
        }
        //这里是题目中说了价格都是10的倍数,剪枝,降低时间复杂度。
        N = N / 10;
        vector<vector<int>> dp(m + 1, vector<int>(1 + N, 0));
        for (int i = 1; i <= m; ++i) {
            for (int j = 1; j <= N; ++j) {
                if (itemType[i - 1] == 0) {
                    vector<int> res;
                    vector<int> slaveIndex;
                    for (int k = 0; k < m; ++k) {
                        if (itemType[k] == i) {
                            slaveIndex.push_back(k);
                        }
                    }
                    int tempVal = dp[i - 1][j];
                    res.push_back(tempVal);
                    if (j >= price[i - 1]) res.push_back(dp[i - 1][j - price[i - 1]] + price[i - 1] * val[i - 1]);
                    if (slaveIndex.size() == 1) {
                        if (price[slaveIndex[0]] + price[i - 1] <= j) {
                            res.push_back(dp[i - 1][j - price[i - 1] - price[slaveIndex[0]]] + price[i - 1] * val[i - 1]
                                          + price[slaveIndex[0]] * val[slaveIndex[0]]);
                        }
                    }
                    if (slaveIndex.size() == 2) {
                        if (price[slaveIndex[0]] + price[i - 1] <= j) {
                            res.push_back(dp[i - 1][j - price[i - 1] - price[slaveIndex[0]]] + price[i - 1] * val[i - 1]
                                          + price[slaveIndex[0]] * val[slaveIndex[0]]);
                        }
                        if (price[slaveIndex[1]] + price[i - 1] <= j) {
                            res.push_back(dp[i - 1][j - price[i - 1] - price[slaveIndex[1]]] + price[i - 1] * val[i - 1]
                                          + price[slaveIndex[1]] * val[slaveIndex[1]]);
                        }
                        if (price[slaveIndex[0]] + price[slaveIndex[1]] + price[i - 1] <= j) {
                            res.push_back(dp[i - 1][j - price[i - 1] - price[slaveIndex[0]] -
                                                    price[slaveIndex[1]]] + price[i - 1] * val[i - 1] + price[slaveIndex[0]] *
                                          val[slaveIndex[0]] + price[slaveIndex[1]] * val[slaveIndex[1]]);
                        }
                    }
                    /*cout<<i<<" "<<j<<": ";
                    for(auto it : res) {
                        cout<<it<<" ";
                    }*/
                    dp[i][j] = *(max_element(res.begin(), res.end()));
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
 /*     for (int i = 0; i <= m; ++i) {
            for (int j = 0; j <= N; ++j) {
                cout << dp[i][j] << " ";
            }
            cout << endl;
        }*/
        cout << 10 * dp[m][N] << endl; //输出值,*10是为了还原剪枝操作
    }
}

成功通过!

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值