题解 | 骨头收藏家

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平均需要计算一次,时间复杂度为
  • 空间复杂度:优化前,优化后
全部评论

相关推荐

晚上和一个老哥聊天,加深了自己对一些事情的思考就是一个人在公共场合敢实名表达自己的感受,自己的思考,自己的观点,是一件需要非常非常大勇气的事情,这意味着你触达内心的想法感受,会被大众所注视,审判,而绝大多数人都会异常在意别人对自己的看法,所以当大规模的眼光都看在你身上的时候,这种压力不是谁都能抗下来的。小一点的是在朋友圈写小作文发表自己内心的想法,之前我是能经常看到不少同学吃一个东西&nbsp;或者&nbsp;去一个地方玩然后长篇大论写下自己感受的朋友圈,但现在我也很少在朋友圈看到这些内容了,大家是长大了,开始忽略这些感受了,还是越来越不愿意拿出来分享了……大一点是直接做自媒体,更大范围地展示自己,直接向全互联网的人述说自己的经历,表达自己的想法,展示自己性感的大脑,让互联网的所有人凝视你,审判你,赞扬你,诋毁你……说实话,这非常像把自己扒光了游街示众的感觉,只有真正在互联网上实名发表过这种口播视频之后,才懂这种感觉有多奇妙哈哈哈我们不说钱不钱的问题,关说对个人能力的提升,这非常锻炼人,非常非常锻炼人,你的表达能力,你的心理素质都在全方面提升,你的心理抗压能力也会不断提升,因为无论怎么说都有人骂你,你说苹果手机好用,都有人骂你叛国贼[捂脸][捂脸][捂脸]一开始最大的障碍就是怕熟人看到,特别尴尬,怕大家议论你,嘲笑你,但其实真的有那么多人关注你吗?真的有那么多人嘲笑你吗?可能都是自己在臆想,出现幻觉了就算真的有人当面嘲笑你,这又怕什么呢,我始终坚信一个真正从0到1在某个领域做成功一件事的人(标准:得到这个领域人的普遍认可)是不会嘲笑一个开始很笨拙的人,因为谁不是这样走过来的?谁一开始就做的很好,谁刚开始做就很随心所欲,是你吗?一个健身大神会嘲笑一个刚入健身房的新手吗?一个高级程序员会嘲笑一个刚学会打hello&nbsp;world的新手吗?一个减肥成功的人会嘲笑刚开始跑几步路就喘的胖子吗?一个作家会嘲笑刚开始写小作文词不达意语句不通顺的菜鸟吗?一个人但凡能嘲笑你,那就证明他没做成哪怕至少一件事,没在一个领域得到绝大多数人的普遍认可,这种人的嘲笑是多么无力,这是他无能的狂怒,他自己不敢,自己半途而废,他怕你做成了,证明他自己是废物而已(这里说话比较难听)从我刚开始从化学跨行当程序员时,我就开始向外展示这些事情,然后无论在现实里还是在互联网上,我都听过非常非常多嘲笑的声音,否定的声音,所以我一度非常敏感,非常脆弱在大二这一年我几乎不敢见人,我每天吃喝拉撒都在实验室的小工位,我怕出去会被人嘲笑,会被否定,因为随随便便一句话我就能蹲在天台哭一晚上,直到我突然进了美团的日常实习,直到我突然进了字节的暑期实习,直到我秋招又拿了字节的offer,这个时候我已经站在高处,我回头看,我向下看,之前那些否定嘲笑的声音早已听不见,我已经在山上了,而他们又在哪呢?而我发现当时那些鼓励我,认可我,支持我的兄弟们,不是那些已经在某个领域取得一定成果的人,就是那些同样在路上的同伴,好像只有这些人,他们才会对蹒跚学步的新手给予鼓励与帮助……最后居然戏剧性地来了一个callback&nbsp;呼应了我大一演小品的一句台词且视他人之疑目如盏盏鬼火大胆地去走你的夜路!这是我自己的亲身经验,也是我想表达,传递的内容,想干什么就去干吧,至于别人怎么看,随他吧,反正弱者才会嘲笑你,强者都会向你伸出援手……
牛友故事会
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

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