题解 | 骨头收藏家

alt

题干分析

题设给予我们一个有限大小的背包,大小为V。同时给予我们n个骨头及其对应的价值与所占空间。求骨头收藏家使用该背包能够获得的最大骨头总价值。

算法思路

拆分问题:

我们首先考虑只装第一块骨头的情况:

  • 背包大小从0~V递增的过程中不难证明当背包大小超过第一块骨头大小时将其塞入为最优策略。

接着我们尝试考虑放前两个骨头:我们只考虑第二块骨头

  • 背包大小V不足以放下第二块骨头时直接继承只放第一块骨头的最优情况
  • 背包大小V足以放下第二块,我们此时有两个选择:放下第二块与不放,不放的情况与只放一块骨头情况一致,放下第二块骨头则背包大小缩小为V-v[2],此时子问题为在大小为V-v[2]大小的背包下只放第一块骨头的最优情况,这是我们先前计算过的值,能够直接读取,此值加上第二块骨头的价值后与另一种情况的总价值进行对比,保留总价值更高的选择。

拆分后我们设数组值dp[i][j]为考虑前i个骨头,在背包大小为j时最优的骨头搜集总价值。由此状态转移方程为:

到此直接双循环嵌套进行实现即可。

优化空间

我们注意到整个DP过程只涉及dp[i][0...j]与dp[i-1][0...j]两组可视作一维数组的空间中,我们不妨复用一部分空间以此降低算法的空间复杂度。

方案有至少两种,一种是直接使用2个大小为V的数组交替使用,这样做的优点是实现简单不易出错,实现细节可以使用模二或者交换枢纽值两种方案实现实现。

另一种方案则更加节省,我们注意到更新状态时同一行的状态值不随计算先后顺序改变而改变,同时计算靠右的状态值时只依赖靠左的值,因此我们只使用一个大小为V的一维数组通过逆序更新的方式即可实现。

实现代码

#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1011;
int w[N], v[N];

// 空间优化2
int dp[N];
int sol(int n, int c) {
    for (int i = 1; i <= n; ++i) {
        for (int j = c; j >= v[i]; --j) {
            dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
        }
    }
    return dp[c];
}//*/

/* // 空间优化1
int dp[2][N];
int sol(int n, int c) {
    for (int i = 1; i <= n; ++i) {
        for (int j = 0; j <= c; ++j) {
            if (v[i] > j) dp[i % 2][j] = dp[(i - 1) % 2][j];
            else dp[i % 2][j] = max(dp[(i - 1) % 2][j], dp[(i - 1) % 2][j - v[i]] + w[i]);
        }
    }
    return dp[n % 2][c];
}//*/

/* // 基本DP
int dp[N][N];

int sol(int n, int c) {
    for (int i = 1; i <= n; ++i) { // 装前i个骨头
        for (int j = 0; j <= c; ++j) { // 共计j大小的空间
            // 第i个物品大小比总空间大,装不下
            if (v[i] > j) dp[i][j] = dp[i - 1][j];
            // 装得下,则选择不装或者装其中的最大价值
            else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]);
        }
    }
    return dp[n][c];
}//*/

int main() {
    int T; cin >> T;
    for (int _ = 0; _ < T; ++_) {
        int n, c;
        cin >> n >> c;
        for (int i = 1; i <= n; ++i) cin >> w[i];
        for (int i = 1; i <= n; ++i) cin >> v[i];
        memset(dp, 0, sizeof(dp));
        cout << sol(n, c) << endl;
    }
    return 0;
}

复杂度分析

  • 时间复杂度:针对前1n个骨头,以及背包大小0c平均需要计算一次,时间复杂度为
  • 空间复杂度:优化前,优化后
全部评论

相关推荐

02-09 03:00
已编辑
门头沟学院 安卓
公司主要是做Flutter业务的,感觉答的一般般,简历上的东西自己还是要熟悉。1.自我介绍2.你在项目中遇到的难点,怎么解决的?3.讲一下MVVM架构,安卓和Flutter中的MVVM框架有什么区别吗?4.原生安卓与Flutter之间的区别。5.假如让你开发一个功能,你会怎么使用AI完成这个功能?6.AI开发中的代码大部分是正常,但是有一部分不正常,你会怎么处理?7.在这个AI写的功能有一部分异常的基础上再开发下一个功能,你会怎么处理?(没答到点上)8.你一般用什么网络框架?(问安卓+Flutter的)9.判断网络请求是否成功或者失败,失败的原因是什么。成功的话,成功会返回什么数据,你会怎么封装这样的网络框架?(答的不好)10.token拦截与自动刷新怎么设计的?(答的不好)11.怎么减少用户加载时间?12.你知道LRU框架,LRU算法吗?(不会,然后面试官和我讲解了几分钟)13.怎么提高APP中的下载速度?14.多线程下载步骤是什么?(没答好)15.多线程怎么下载不同的片?16.怎么校验一个文件是不是原来的文件?有没有下载成功?17.写鸿蒙体验怎么样?对比安卓有什么区别?18.AI怎么写鸿蒙?相比于别的是不是比较难写?19.用Flutter开发怎么兼容鸿蒙?20.怎么分别管理安卓和ios、鸿蒙的SDK?21.有些SDK是不兼容鸿蒙的,只兼容安卓、ios的SDK,你怎么处理?22.鸿蒙有个封装了sqlite的库,你怎么把他引入到Flutter项目中,尽量不改变原有,又保证对安卓、ios的侵入性是最小的?(答的不好)23.反问环节。对我们这个项目有什么想问的吗、谈薪等。
查看22道真题和解析
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

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