背包问题学习笔记-01背包

news2025/1/13 16:46:59

背景

背包问题是动态规划问题中的一个大类,学习背包问题对于掌握动态规划十分重要。背包问题也很容易成为程序员算法面试中的一个槛,但其实背包问题已经被研究,讲解的比较成熟了,在这些丰富的讲解资料的基础之上,大家理解背包问题的难度也被大大减弱了。

acwing背包问题列表

本篇笔记主要参考了 AcWing 上的题目列表以及讲解视频,原因有二:1)上面截图中相关的问题都是免费的,不需要会员。2)AcWing 作者的讲解较为细致,适合新手学习。

题意描述:

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i  件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤1000

0<vi,wi≤1000

示例:

4 5
1 2
2 4
3 4
4 5

8


解题思路:

Alice: 01 背包有思路吗 ?
Bob: 我记得这个是动态规划,好像还是二维动态规划 ?
Alice: 状态转移方程能找到吗 ?
Bob: 找状态转移方程之前得先明确要记录的状态是啥,dp[i][j] 是什么意思吧 ?
Alice: 说的对,dp[i][j] 的值一般的话,应该就是要求解的值吧,就是最大价值。
Bob: 那 i 和 j 呢 ?还剩下的变量是什么 ?物品的体积,物品的 ID ? 背包的体积 ?
Alice: 背包的体积是个常量,背包中可用的体积在状态转移的时候是个变量。
Bob: 那让 dp[i][j] 是前 i 个物品,在背包剩余体积是 j 的状态下的最大价值 ?
Alice: 不应该绕一道,让 dp[i][j] 是前 i 个物品在消耗了 j 的体积下的最大价值就好了。这样的话, dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[j]] + w[j] )
Bob: 我想想 🤔️,第 i 个物品不装入背包的时候,最大价值就是前 i-1 个物品在 j 的体积下的最大价值,就是 dp[ i - 1 ][j] 。第 i 个物品装入背包的时候,装的下吗 ?如果要装的下的话,应该是 dp[i-1][j - v[j]] + w[j], 也就是说前 i-1 个物品最多消耗 j-v[j] 的空间,这样才放得下,然后加上第 j 个物品的价值 w[j] 就可以了。
Alice: 对,应该没问题。
Bob: 具体的求解过程呢 ?初始化一个二维数组,一维是物体数量,二维是背包体积,初始化都是 0,然后从上到下,从左到右按照上面的 递推公式求解二维数据,最后整个数组的最大值就是答案 ?
Alice: 我去试试 💃 … 果然过了。
Bob: 计算的过程感觉还是要注意一下,初始化的时候还要先求解第一行的。


Alice: 听说还有什么状态压缩 ?
Bob: 减少二维数组消耗的内存 ?把 n*m 的数据改成 2 * m 的,因为每次计算只有两行之间的递推关系。
Alice:有点麻烦,需要来回的覆写数组。
Bob: 难道是能改成一维的动态规划 !!
Alice: 应该是吧,dp[i][j] 表示的是前 i 个物品消耗体积小于等于 j 时的最大价值,改成一维的话,i 和 j 应该保留哪个呢 ?
Bob: 哪个能完成状态转移就保留哪个吧,值肯定还是最大价值。保留体积 j 的话,dp[j] = max(dp[j], dp[j-v[i]] + w[i]) ,是这样的吗 ?
Alice: 怎么还有 i 呢 ?
Bob: 当然还有 i, 状态转移还是第 i 个物品要不要装进去。双循环是不变的,只是优化是有的二维数组的空间。
Alice:你去试试 ?
Bob: 有个麻烦的点,很难理解。计算体积的时候要反向计算,因为 dp[j] 依赖于 dp[j-ivolum],而这两个东西是在一个一维数组里面不断更新的,所要要先更新 dp[j] 再更新 dp[j-ivolum],不然就算不对了。
Alice:确实 🤔️


Alice: 还有一件事,我听嗦状态压缩之后根本不用求最大值,dp[maxVolumn] 就是最大值,是真的吗 ?
Bob: 这个和你初始化的方式有关系,如果 dp 全都初始化成 0,那 dp[maxVolumn] 就是最大值,否则不一定。
Alice: 这么神奇的吗 ?
Bob: 最的价值不一定把背包填满了,假设最大价值时消耗的空间是 k,且 k < maxVolumn 想一下这个时候的状态转移。
Alice: 如果不把 dp 都初始化为 0,那应该怎么初始化,dp[0] = 0,其他空间初始化成负无穷 ?
Bob: 对,这样在算的时候 dp[j] = max(dp[j], dp[j-v[i]] + w[i]) ,如果 j-v[i] 不是一个有效的填充体积,那 dp[j] 就还是负无穷,只有有效的填充体积才能有价值。这个时候是需要求最大值的。
Alice: 我去试试
Bob: 给你个测试用例

5 4
2 2
2 4
3 4
4 5
3 7

dp 算完应该是这样的 [ 0, -Infinity, 4, 7, 6 ]


代码:

二维动态规划 + js

const fs = require('fs');
let buffer = '';

process.stdin.on('readable', () => {
    const chunk = process.stdin.read();
    if (chunk) {
        buffer += chunk.toString()
    }
});

// 输入的字符串转换为数字
const convert = (inputString) => {
    const list = [];
    inputString.split('\n').forEach((line) => {
        const tokens = line.split(' ');
        list.push(tokens.map(num => parseInt(num, 10)));
    });
    return list;
}

// 批量调用
const batchCall = (list, solve) => {
    // 划分数据
    const data = [];
    let countAndVolumIndex = 0;
    while(countAndVolumIndex < list.length) {
        const [count, volum] = list[countAndVolumIndex];
        data.push({
            volum: volum,
            count: count,
            volumAndWeight: list.slice(countAndVolumIndex + 1, countAndVolumIndex + 1 + count)
        });
        countAndVolumIndex += count + 1;
    }
    
    data.forEach(item => {
        // 防止空行或者无效数据
        if(solve && item && item.count && item.volum) {
            solve(item.count, item.volum, item.volumAndWeight);
        }
    });
}


const solve = (count, maxVolum, volumAndWeight) => {
    const dp = [];
    for(let i=0; i<=count; ++i) {
        dp.push(new Array(maxVolum + 1).fill(0));
    }
    
    // 初始化第一行
    const [firstThingVolum, firstThingWeight] = volumAndWeight[0];
    for(let j=0; j<=maxVolum; ++j) {
        dp[0][j] = j >= firstThingVolum ? firstThingWeight : 0;
    }
    
    let result = 0;
    for(let i=1; i<count; ++i){
        for(let j=0; j<=maxVolum; ++j) {
            // 第 i 个物品的体积和价值
            const [ivolum, iweight] = volumAndWeight[i];
            dp[i][j] = dp[i-1][j];
            if (j >= ivolum) {
                dp[i][j] = Math.max(
                    dp[i-1][j],
                    dp[i-1][j-ivolum] + iweight
                )
            }
            result = Math.max(result, dp[i][j]);
        }
    }
    console.log(result);
}

process.stdin.on('end', function() {
    batchCall(convert(buffer), solve)
});

状态压缩 + js

const fs = require('fs');
let buffer = '';

process.stdin.on('readable', () => {
    const chunk = process.stdin.read();
    if (chunk) {
        buffer += chunk.toString()
    }
});

// 输入的字符串转换为数字
const convert = (inputString) => {
    const list = [];
    inputString.split('\n').forEach((line) => {
        const tokens = line.split(' ');
        list.push(tokens.map(num => parseInt(num, 10)));
    });
    return list;
}

// 批量调用
const batchCall = (list, solve) => {
    // 划分数据
    const data = [];
    let countAndVolumIndex = 0;
    while(countAndVolumIndex < list.length) {
        const [count, volum] = list[countAndVolumIndex];
        data.push({
            volum: volum,
            count: count,
            volumAndWeight: list.slice(countAndVolumIndex + 1, countAndVolumIndex + 1 + count)
        });
        countAndVolumIndex += count + 1;
    }
    
    data.forEach(item => {
        if(solve && item && item.count && item.volum) {
            solve(item.count, item.volum, item.volumAndWeight);
        }
    });
}


const solve = (count, maxVolum, volumAndWeight) => {
    const dp = new Array(maxVolum + 1).fill(0);
    
    let result = 0;
    for(let i=0; i<count; ++i){
        // 为何这里是从大到小反向计算呢 ?
        // 从第二个物品开始思考,第一个物品计算完了的时候,dp[j] 就是 dp[i-1][j], 
        // 现在我们要更新 dp[j] 的数据,从右往左更新,是因为右依赖左,
        // dp[j] 的计算要依赖 dp[j-ivolum],所以要保证计算 dp[j] 的时候,
        // dp[j-ivolum] 还是 i-1 的时候的值。
        for(let j=maxVolum; j>=0; --j) {
            // 第 i 个物品的体积和价值
            const [ivolum, iweight] = volumAndWeight[i];
            if (j >= ivolum) {
                dp[j] = Math.max(
                    dp[j],
                    dp[j-ivolum] + iweight
                )
            }
            
            result = Math.max(result, dp[j]);
        }
    }

    console.log(result);
}

process.stdin.on('end', function() {
    batchCall(convert(buffer), solve)
});

状态压缩 + 是否精确求解 + js

dp[j] 为体积恰好等于 j 时的最大价值

const solve = (count, maxVolum, volumAndWeight) => {
    const dp = new Array(maxVolum + 1).fill(-1 * Infinity);
    dp[0] = 0;
    
    for(let i=0; i<count; ++i){
        for(let j=maxVolum; j>=0; --j) {
            // 第 i 个物品的体积和价值
            const [ivolum, iweight] = volumAndWeight[i];
            if (j >= ivolum) {
                dp[j] = Math.max(
                    dp[j],
                    dp[j-ivolum] + iweight
                )
            }
        }
    }
    console.log(Math.max(...dp));
}

dp[j] 为体积 <= j 时的最大价值

const solve = (count, maxVolum, volumAndWeight) => {
    const dp = new Array(maxVolum + 1).fill(0);
    
    for(let i=0; i<count; ++i){
        for(let j=maxVolum; j>=0; --j) {
            // 第 i 个物品的体积和价值
            const [ivolum, iweight] = volumAndWeight[i];
            if (j >= ivolum) {
                dp[j] = Math.max(
                    dp[j],
                    dp[j-ivolum] + iweight
                )
            }
        }
    }

    console.log(dp[maxVolum]);
}

参考:

  • 题目链接
  • 讲解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1016755.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

图注意网络(GAT)的可视化实现详解

能够可视化的查看对于理解图神经网络(gnn)越来越重要&#xff0c;所以在这篇文章中&#xff0c;我将介绍传统GNN层的实现&#xff0c;然后展示ICLR论文“图注意力网络”中对传统GNN层的改进。 假设我们有一个表示为有向无环图(DAG)的文本文档图。文档0与文档1、2和3有一条边&am…

第72步 时间序列建模实战:单步滚动预测(以决策树回归为例)

基于WIN10的64位系统演示 一、写在前面 从这一期开始&#xff0c;我们开始基于python构建各种机器学习和深度学习的时间序列预测模型&#xff0c;本质上就是调用各种模型的回归分析的属性。所以很多模型其实之前都介绍过&#xff0c;比如说决策树、SVM等等。 同样&#xff0…

【踩坑篇】代码中使用 Long 作为 Map的Key存在的问题

本周的工作结束&#xff0c;详述一些在项目代码中实际遇到的一些坑。 代码中遇到这样一个场景&#xff1a; 有个业务接口&#xff0c;接口返回的值是一个JSON格式的字符串&#xff0c;通过JSON解析的方式&#xff0c;解析为格式为&#xff1a; Map<Long, Map<String, O…

数据结构——时间复杂度与空间复杂度

目录 一.什么是空间复杂度与时间复杂度 1.1算法效率 1.2时间复杂度的概念 1.3空间复杂度的概念 二.如何计算常见算法的时间复杂度 2.1大O的渐近表示法 使用规则 三.如何计算常见算法的空间复杂度 3.1 大O渐近表示法 3.2 面试题——消失的数字 3.3 面试题——旋转数组 一…

ChatGPT是留学生的论文神器还是学术不端的罪魁祸首?

当今时代&#xff0c;ChatGPT无疑是大数据和人工智能的完美结合&#xff0c;成为了搜索技术的革命性创新。几秒钟&#xff0c;一篇逻辑清晰、观点鲜明、有充分论据支持的文章即可生成。这种革命性的创新在学术界掀起了巨大的浪潮&#xff0c;甚至让全球的学校开始思考&#xff…

【GAN入门】生成 AI的概念

一、说明 GAN是生成对抗网络&#xff08;Generative Adversarial Network&#xff09;的缩写&#xff0c;是一种无监督学习算法&#xff0c;由Goodfellow等人于2014年提出。GAN由一个生成器网络和一个判别器网络组成&#xff0c;通过二者之间的对抗来训练生成器网络生成与真实样…

深入了解Python数据类型及应用

Python提供了一组丰富的内置数据类型&#xff0c;使您能够在程序中处理不同类型的数据。核心数值类型包括整数、浮点数和复数。整数表示整数&#xff0c;对于精确的计数和计算非常有用。 浮点数表示具有小数精度的实数&#xff0c;这对科学和统计计算非常重要。复数将数字扩展到…

C++系列赋值运算符重载

赋值运算符重载 类的默认函数拷贝构造函数和赋值运算符 重载赋值运算符相关注意事项 类的默认函数 一个类至少有4个默认函数&#xff1a; 默认构造函数拷贝构造函数析构函数赋值运算符重载函数 拷贝构造函数和赋值运算符 拷贝构造函数是在创建类的时候调用的&#xff0c;之…

利用PCA科学确定各个指标的权重系数

背景参考: 1、提取主成分 对样本进行PCA分析,查看不同变量贡献率,确定主要的指标。我们可以通过下列代码获取需要的所有数据: import numpy as np from sklearn.decomposition import PCA# 创建一个数据 np.random.seed(0) data = np.random.random((100,5)) y = np.ra…

深入理解CI/CD流程:改变你的开发生命周期

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

投后管理系统的主要功能及开发

投后管理系统是一种用于跟踪和管理投资组合中的投资的工具&#xff0c;通常由私募股权、风险投资公司、资产管理公司和投资者使用。其主要功能包括以下内容&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合…

全新跑分软件GeekRUN-7问世

实测非常好用CPU跑分神器点击下载 感兴趣的可以测测你的手机跑的多少。 我的峰值是7340&#xff0c;低值是4685&#xff0c;测试时后台不能有任何APP

丙烯酸共聚聚氯乙烯树脂

声明 本文是学习GB-T 42790-2023 丙烯酸共聚聚氯乙烯树脂. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本文件规定了丙烯酸共聚聚氯乙烯树脂的外观、物化性能等技术要求&#xff0c;描述了相应的采样、试验方 法、检验规则、标志、包装、…

2023年中国场馆产业研究报告

第一章 行业综述 1.1 定义与分类 场馆&#xff0c;作为一个多元化和充满活力的行业&#xff0c;为人们提供了一个为不同目的而聚集的空间。无论是为了活动、表演、展览还是聚会&#xff0c;场馆都在为社区的社会、文化和经济建设做出了不可或缺的贡献。 场馆是一个为举办各类…

深入思考redis面经

1 redission分布式锁 1.1 为了保证数据一致性&#xff0c;引入了redission的锁&#xff0c;你是为了抗住高并发而去为了引入这个锁还是说为了保证数据一致性去引入的 答&#xff1a;主要是为了抗住高并发问题&#xff0c;解决redis的缓存击穿问题&#xff0c;但是也能解决一定…

【算法|链表】环形链表Ⅱ

环形链表Ⅱ 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统…

原创 VTK 基础入门 ( 一 ) 贴纹理

纹理 这个示例先读入一幅JPEG的二维纹理图;然后定义一个纹理类vtkTexture对象&#xff0c;接着把读入的NPG图像输入到vtkTexture 里&#xff0c; 作为它即将“贴”到平面上的一个纹理图;再定义一个vtkPlaneSource对象&#xff0c;类vtkPlaneSource 可以生成一个…

Pytorch学习:torch.max(input,dim,keepdim=False)

文章目录 torch.max()dimkeepdimdim0dim1 out&#xff1a;返回命名元组 (values, indices) torch.max() torch.max(input) → Tensor&#xff1a;返回 input 张量中所有元素的最大值。 注意输入的必须是张量形式&#xff0c;输出的也为张量形式 当输入为tuple类型时&#xf…

Attention is all you need 论文笔记

该论文引入Transformer&#xff0c;主要核心是自注意力机制&#xff0c;自注意力&#xff08;Self-Attention&#xff09;机制是一种可以考虑输入序列中所有位置信息的机制。 RNN介绍 引入RNN为了更好的处理序列信息&#xff0c;比如我 吃 苹果&#xff0c;前后的输入之间是有…

计算机组成原理之计算机系统概论、计算机的发展史、系统总线,三章开篇讲

第一章-计算机系统概论 1计算机系统简介 现代计算机的多态性 把感应器嵌入和装备到电网、铁道、桥梁、隧道、公路、建筑、供水系统、大坝、油气管道等各种物体中&#xff0c;并且被普遍连接&#xff0c;形成“物联网”&#xff0c;然后将“物联网”与现有的网络整合起来&…