题解 | #购物单#

购物单

http://www.nowcoder.com/practice/f9c6f980eeec43ef85be20755ddbeaf4

HJ16 购物单 题解

by 限时烟花

1. 抽丝剥茧

看到题目的第一想法就是很像背包问题,如果不考虑“附件”的问题,那么就是0-1背包问题。
一开始觉得要考虑附件好像整个问题就会变得复杂,还挺头痛的。
但是所谓附件,表面上在制造问题,但是实际上也真的就是“附件”。

2. 化繁为简

我们可以这样理解,对于同一个物品,现在它的价格、重要度都是可变的
那么我们只需要对每一个主件尝试如下四种情况:

  1. 仅加入一个主件;
  2. 加入主件和第一个附件;
  3. 加入主件和第二个附件;
  4. 加入主件和两个附件;

在以上四种情况中找到最大值就能回归到0-1背包问题。

所以我们先考虑一下普通的0-1背包问题

对于一个可承重C的背包,我们假设所有物品的重量数据保存在w[],所有价值数据保存在v[]。那么我们有以下的推导式:

dp[i][j]=max(dp[i1][j],dp[i1][jw[j]]+v[j])dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[j]] + v[j])

那么对于“购物单”这道题,我们可以有如下抽象:

dp[i][j]=max(dp[i1][j], {})dp[i][j] = max(dp[i-1][j],\ \{四种情况产生的价值\})
{}={}\{四种情况产生的价值\} = \{仅加入主件,加入主件和第一个附件,加入主件和第二个附件,加入主件和两个附件\}

3. 码上行动

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

using namespace std;

int main(){
    int N, m; // N 奖金 m 物品个数
    cin >> N >> m;
    N /= 10; // 由于所有的价格都是10的整倍数,所以可以均除10以简化运算复杂度

    int price, priority, hasAttachments;
    // 使用一个(m+1)X6的数组存储数据,m+1是根据物品编号,0作废;6考虑可能有附件的最多的情况
    vector<vector<int>> data(m+1, vector<int>(6, 0));
    
    for(int i = 1; i <= m; i++){
        cin >> price >> priority >> hasAttachments;
        // 主件
        if(hasAttachments == 0){
            data[i][0] = price/10;
            data[i][1] = priority;
        }
        // 第一个附件
        else if(data[hasAttachments][2] == 0){
            data[hasAttachments][2] = price/10;
            data[hasAttachments][3] = priority;
        }
        // 第二个附件
        else {
            data[hasAttachments][4] = price/10;
            data[hasAttachments][5] = priority;
        }
    }

    vector<vector<int>> dp(m+1, vector<int>(N+1, 0));
    for(int i = 1; i < m+1; i++){
        for(int j = 1; j < N+1; j++){
            int pricePrime = data[i][0];
            int priceAtta1 = data[i][2];
            int priceAtta2 = data[i][4];
            
            int priorPrime = data[i][1];
            int priorAtta1 = data[i][3];
            int priorAtta2 = data[i][5];

            dp[i][j] = j >= pricePrime ? max(dp[i-1][j - pricePrime] 
                                            + priorPrime * pricePrime, 
                                            dp[i-1][j]) : dp[i-1][j];
            dp[i][j] = j >= (pricePrime + priceAtta1) ? max(dp[i-1][j - pricePrime - priceAtta1]
                                                        + priorPrime * pricePrime 
                                                        + priorAtta1 * priceAtta1, 
                                                        dp[i][j]) : dp[i][j];
            dp[i][j] = j >= (pricePrime + priceAtta2) ? max(dp[i-1][j - pricePrime - priceAtta2]
                                                        + priorPrime * pricePrime 
                                                        + priorAtta2 * priceAtta2, 
                                                        dp[i][j]) : dp[i][j];
            dp[i][j] = j >= (pricePrime + priceAtta1 + priceAtta2) ? 
                                                        max(dp[i-1][j - pricePrime - priceAtta1 - priceAtta2]
                                                        + priorPrime * pricePrime 
                                                        + priorAtta1 * priceAtta1
                                                        + priorAtta2 * priceAtta2, 
                                                        dp[i][j]) : dp[i][j];
        }
    }
    cout << dp[m][N] * 10 <<endl;
    return 0;
}

dp数组更新的可视化过程如下: alt

4. 心有成算

  1. 空间复杂度:dp数组的空间大小为mN,数据vector的大小是(m+1)×\times6,故空间复杂度为O(mN)O(mN);
  2. 时间复杂度:因为是对dp数组的所有元素进行一边遍历,故空间复杂度为O(mN)O(mN)

5. 精益求精

解法一中比较大的问题是空间复杂度,使用的dp数组的大小是(m+1)×(N+1)(m+1) \times (N+1),这使得空间复杂度非常大。同时根据解法我们可以看到,对于任意的dp[i][j]dp[i][j]它只与dp[i1][  ]dp[i-1][\ \ ](即第i1i-1行)有关。所以我们可以考虑对dp数组的大小进行优化。 使用单行的dp数组来保存上一行的dp状态,并且大小可以以输入的N为基准。

改进过后的代码如下:

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

using namespace std;

int main(){
	int N, m; // N 奖金 m 物品个数
    cin >> N >> m;
    N /= 10; // 由于所有的价格都是10的整倍数,所以可以均除10以简化运算复杂度

    int price, priority, hasAttachments;
    vector<vector<int>> data(m+1, vector<int>(6, 0));

    for(int i = 1; i <= m; i++){
        cin >> price >> priority >> hasAttachments;
        if(hasAttachments == 0){
            data[i][0] = price/10;
            data[i][1] = priority;
            // count++;
        }
        else if(data[hasAttachments][2] == 0){
            data[hasAttachments][2] = price/10;
            data[hasAttachments][3] = priority;
        }
        else {
            data[hasAttachments][4] = price/10;
            data[hasAttachments][5] = priority;
        }
    }

	vector<int> dp(N+1, 0);
    for(int i = 1; i < m+1; i++){
        for(int j = N; j >= 1; j--){
            int pricePrime = data[i][0];
            int priceAtta1 = data[i][2];
            int priceAtta2 = data[i][4];
            
            int priorPrime = data[i][1];
            int priorAtta1 = data[i][3];
            int priorAtta2 = data[i][5];

            dp[j] = j >= pricePrime ? max(dp[j - pricePrime]
                                            + priorPrime * pricePrime, 
                                            dp[j]) : dp[j];
            dp[j] = j >= (pricePrime + priceAtta1) ? max(dp[j - pricePrime - priceAtta1]
                                                        + priorPrime * pricePrime 
                                                        + priorAtta1 * priceAtta1, 
                                                        dp[j]) : dp[j];
            dp[j] = j >= (pricePrime + priceAtta2) ? max(dp[j - pricePrime - priceAtta2]
                                                        + priorPrime * pricePrime 
                                                        + priorAtta2 * priceAtta2, 
                                                        dp[j]) : dp[j];
            dp[j] = j >= (pricePrime + priceAtta1 + priceAtta2) ? 
                                                        max(dp[j - pricePrime - priceAtta1 - priceAtta2]
                                                        + priorPrime * pricePrime 
                                                        + priorAtta1 * priceAtta1
                                                        + priorAtta2 * priceAtta2, 
                                                        dp[j]) : dp[j];
        }
    }
    cout << dp[N] * 10 <<endl;
    return 0;
}

复杂度分析

  1. 时间复杂度:没有改变,仍为O(mN)O(mN);
  2. 空间复杂度:O(N)O(N)
全部评论
同时放入主件和附件 为啥状态转移物品书还是减1 这不是两个或3个物品了吗?比如以下: 为什么不是这样呢?dp[i][j] = j>=(a+c) && i>=2 ? max(dp[i][j],dp[i-2][j-a-c]+b+d) : dp[i][j];
2 回复 分享
发布于 2023-07-26 11:27 湖南
讲得真好
点赞 回复 分享
发布于 2024-01-25 01:11 重庆
data[i][]有部分i对应的值没数据浪费空间了,管得了吗。
点赞 回复 分享
发布于 2023-12-02 12:50 云南
终于找到一个看懂的
点赞 回复 分享
发布于 2023-06-23 16:58 陕西
请问一下,这个data[][]数组的作用,定义什么的吗?可以详细解释下嘛?不是很懂?
点赞 回复 分享
发布于 2023-05-08 15:58 广东
终于看懂了 讲的真好啊
点赞 回复 分享
发布于 2022-04-24 01:37
666
点赞 回复 分享
发布于 2022-04-19 20:40
大佬666,看到你的这个总算是看懂是怎么回事了
点赞 回复 分享
发布于 2022-03-29 23:50

相关推荐

06-13 17:33
门头沟学院 Java
顺序不记了,大致顺序是这样的,有的相同知识点写分开了1.基本数据类型2.基本数据类型和包装类型的区别3.==和equals区别4.ArrayList与LinkedList区别5.hashmap底层原理,put操作时会发生什么6.说出几种树型数据结构7.B树和B+树区别8.jvm加载类机制9.线程池核心参数10.创建线程池的几种方式11.callable与runnable区别12.线程池怎么回收线程13.redis三剑客14.布隆过滤器原理,不要背八股,说说真正使用时遇到了问题没有(我说没有,不知道该怎么回答了)15.堆的内存结构16.自己在写项目时有没有遇见过oom,如何处理,不要背八股,根据真实经验,我说不会17.redis死锁怎么办,watchdog机制如何发现是否锁过期18.如何避免redis红锁19.一个表性别与年龄如何加索引20.自己的项目的QPS怎么测的,有没有真正遇到大数量表21.说一说泛型22.springboot自动装配原理23.springmvc与springboot区别24.aop使用过嘛?动态代理与静态代理区别25.spring循环依赖怎么解决26.你说用过es,es如何分片,怎么存的数据,1000万条数据怎么写入库中27.你说用limit,那么在数据量大之后,如何优化28.rabbitmq如何批次发送,批量读取,答了延迟队列和线程池,都不对29.计网知不知道smtp协议,不知道写了对不对,完全听懵了30.springcloud知道嘛?只是了解反问1.做什么的?短信服务,信息量能到千万级2.对我的建议,基础不错,但是不要只背八股,多去实际开发中理解。面试官人不错,虽然没露脸,但是中间会引导我回答问题,不会的也只是说对我要求没那么高。面完问我在济宁生活有没有困难,最快什么时候到,让人事给我聊薪资了。下午人事打电话,问我27届的会不会跑路,还在想办法如何使我不跑路,不想扣我薪资等。之后我再联系吧,还挺想去的😭,我真不跑路哥😢附一张河科大幽默大专图,科大就是大专罢了
查看30道真题和解析
点赞 评论 收藏
分享
评论
132
23
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务