【算法】经典背包问题

news2025/1/19 11:21:01

作者:指针不指南吗
专栏:算法篇

🐾或许会很慢,但是不可以停下来🐾

文章目录

  • 引入Dp
  • 1.01背包
  • 2.完全背包
  • 3.多重背包
  • 4.分组背包

acwing 背包问题——学习笔记

01背包、完全背包、多重背包、分组背包

引入Dp

Dp问题,先写出基本形式,然后优化,对代码进行等价变形

  • 下面是Dp问题的分析基本流程

在这里插入图片描述

1.01背包

问题描述:

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

第 i 件物品的体积是 v i v_i vi,价值是 w i w_i wi

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

输出最大价值。

(1)问题分析

「0-1 背包」即是不断对第 i 个物品的做出决策,「0-1」正好代表不选与选两种决定。

在这里插入图片描述

(2)代码实现

①基础版——二维

  • f[i][j] 表示前 i 个物品,背包容量 j 下的最优解(最大价值)

  • 当前背包容量不够(j < v[i]),没得选,因此前 i 个物品最优解即为前 i−1 个物品最优解:f[i][j]=f[i-1][j]

  • 当前背包容量够,两种选择:第 i 个物品,放或者不放,取最大值:max(f[i - 1][j], f[i - 1][j - v[i]] + w[i])

    代码如下:

    #include<bits/stdc++.h>
    using namespace std;
    
    const int N=1010; 
    
    int n,m; //n表示物品个数,m表示最大容量
    int v[N],w[N];  //v表示体积,w表示价值
    int f[N][N]; //f[i][j]表示前i个物体,前j个容量的最大价值
    
    int main()
    {
        cin>>n>>m;
    
        for(int i=1;i<=n;i++)
            cin>>v[i]>>w[i];
    
        //f[0][0~m] 表示0个物体,容量都是0,因为是全局变量,省略
    
        for(int i=1;i<=n;i++) 
            for(int j=0;j<=m;j++)
            {
                f[i][j]=f[i-1][j];
                if(j>=v[i]) f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
            }
    
        cout<<f[n][m]<<endl;
    
        return 0;
    }
    

②优化版——一维

  • 整个转移方程中对于 i 这一维,只用到了i -1, 所以不需要记录所有的f[i],只需要用单个变量记录即可(滚动数组),去除 f 数组的 i 这一维

  • j 使用逆序枚举。如果我们按照正序枚举背包容量 j,即从小到大枚举,那么在更新 f[i][j] 时,可能会使用到 f[i][j-w[i]] 这个状态,其中 w[i] 表示第 i 个物品的重量。这相当于在容量为 j-w[i] 的情况下再次放入物品 i,与题目要求的 0/1 背包问题不符。

  • 因此,为了避免每个物品多次被放入背包的情况,我们采用逆序枚举背包容量 j 的方式更新 DP 状态。在逆序枚举的过程中,我们保证 f[i][j]f[i][j-w[i]] 之前被更新,从而确保每个物品只会被放入背包一次。

    代码如下:

    #include<bits/stdc++.h>
    using namespace std;
    
    const int N=1010; 
    
    int n,m; //n表示物品个数,m表示最大容量
    int v[N],w[N];  //v表示体积,w表示价值
    int f[N];
    
    int main()
    {
        cin>>n>>m;
    
        for(int i=1;i<=n;i++)
            cin>>v[i]>>w[i];
    
       //优化成一维数组
        for(int i=1;i<=n;i++) 
            for(int j=m;j>=v[i];j--)
            {
               f[j]=max(f[j],f[j-v[i]]+w[i]);
            }
    
        cout<<f[m]<<endl;
    
        return 0;
    }
    

2.完全背包

问题描述:

有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。

第 i 种物品的体积是 v i v_i vi ,价值是 w i w_i wi

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

输出最大价值。

(1)问题分析

在这里插入图片描述

(2)代码实现

①基础版——二维/三重循环

  • 问题分析部分已经讲的很清楚了

    代码如下:

    #include<bits/stdc++.h>
    using namespace std;
    
    const int N=1010; 
    
    int n,m; //n表示物品个数,m表示最大容量
    int v[N],w[N];  //v表示体积,w表示价值
    int f[N][N]; //f[i][j]表示前i个物体,前j个容量的最大价值
    
    int main()
    {
        cin>>n>>m;
    
        for(int i=1;i<=n;i++)
            cin>>v[i]>>w[i];
    
        for(int i=1;i<=n;i++) 
            for(int j=0;j<=m;j++)
                for(int k=0;k*v[i]<=j;k++) //k表示第i个物品数量
                    f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
    
        cout<<f[n][m]<<endl;
    
        return 0;
    }
    

②优化版——一维/两重循环

  • 发现规律

    f[i,j]=max(f[i-1,j],f[i-1,j-v]+w,f[i-1,j-2*v]+2*w,f[i-1,j-3*v]+3*w,...)
    f[i,j-v]=max(       f[i-1,j-v]  ,f[i-1,j-2*v]+ w ,f[i-1,j-3*v]+2*w,...)
    由上两式,可得出如下递推关系: 
                            f[i][j]=max(f[i,j-v]+w,f[i-1][j]) 
    

    进行优化

    for(int i = 1 ; i <=n ;i++)
    for(int j = 0 ; j <=m ;j++)
    {
        f[i][j] = f[i-1][j];
        if(j-v[i]>=0)
            f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
    }
    
  • 与01背包进行对比

    f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]) 01背包

    f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]) 完全背包问题

    并进一步优化

     for(int i = 1 ; i<=n ;i++)
        for(int j = v[i] ; j<=m ;j++)//这里的j是从小到大枚举,和01背包不一样
        {
                f[j] = max(f[j],f[j-v[i]]+w[i]);
        }
    

    代码如下:

    #include<bits/stdc++.h>
    using namespace std;
    
    const int N=1010; 
    
    int n,m; //n表示物品个数,m表示最大容量
    int v[N],w[N];  //v表示体积,w表示价值
    int f[N];
    
    int main()
    {
        cin>>n>>m;
    
        for(int i=1;i<=n;i++)
            cin>>v[i]>>w[i];
    
        for(int i=1;i<=n;i++) 
            for(int j=v[i];j<=m;j++)
                f[j]=max(f[j],f[j-v[i]]+w[i]);
    
        cout<<f[m]<<endl;
    
        return 0;
    }
    

3.多重背包

问题描述:

有 N 种物品和一个容量是 V 的背包。

第 i 种物品 最多有 s i s_i si 件,每件体积是 v i v_i vi ,价值是 w i w_i wi

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

输出最大价值。

(1)问题分析

在这里插入图片描述

(2)代码实现

①基础版——二维/三重循环

  • 上面问题分析讲的很清楚

    代码如下:

    #include<bits/stdc++.h>
    using namespace std;
    
    const int N=110;
    
    int n,m;
    int v[N],w[N],s[N];
    int f[N][N];
    
    int main()
    {
        cin>>n>>m;
        
        for(int i=1;i<=n;i++)   cin>>v[i]>>w[i]>>s[i];
        
        for(int i=1;i<=n;i++)
            for(int j=0;j<=m;j++)
                for(int k=0;k<=s[i]&&k*v[i]<=j;k++) //两重限制条件
                    f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
        
        cout<<f[n][m]<<endl;
        
        return 0;
    }
    

②优化版—一维 /二进制

  • 当数据很大时, O ( n 3 ) O(n^3) O(n3) 会超时,进行二进制优化

    题解链接: AcWing 5. 二进制优化,它为什么正确,为什么合理,凭什么可以这样分?? - AcWing

    讲一下为什么二进制优化可以哈。

    题目的意思是某物品最多有s件,我们需要从所有的物品中选择若干件,使这个背包的价值最大。题目并没有说某物品一定需要选多少件出来,也没有说一共要选多少件出来。只是选择若干件,至于选几件,无所谓,但要保证价值最大。

    按照优化的策略某物品有s件,我们给其打包分成了好几个大的物品。

    第一个大物品是包含原来该物品的1件,第二个大物品是包含原来该物品的2件,第三个大物品是包含原来该物品的4件,第四个大物品是包含原来该物品的8件,…依次类推。此时我们就把所有的物品都重新进行了一个分类。

    原先每个物品最多s件,我们就把这个件数条件给消去了。取而代之的是,按照一定原先件数组合出来的新若干大物品。

    我们又已知按照我们划分成大物品进行搭配组合,一定能转化为原先的若干件小物品出来。

    并且选择某物品的最多件数,是不会超过原先该物品的s件。所以就转化为从下面这些若干件大物品中,选择能使背包容积最大大的情况下,价值最高。这个就是一个01问题。转自知名网友评论

    代码如下:

    #include<iostream>
    using namespace std;
    
    const int N = 12010, M = 2010;
    
    int n, m;
    int v[N], w[N]; //逐一枚举最大是N*logS
    int f[M]; // 体积<M
    
    int main()
    {
        cin >> n >> m;
        int cnt = 0; //分组的组别
        for(int i = 1;i <= n;i ++)
        {
            int a,b,s;
            cin >> a >> b >> s;
            int k = 1; // 组别里面的个数
            while(k<=s)
            {
                cnt ++ ; //组别先增加
                v[cnt] = a * k ; //整体体积
                w[cnt] = b * k; // 整体价值
                s -= k; // s要减小
                k *= 2; // 组别里的个数增加
            }
            //剩余的一组
            if(s>0)
            {
                cnt ++ ;
                v[cnt] = a*s; 
                w[cnt] = b*s;
            }
        }
    
        n = cnt ; //枚举次数正式由个数变成组别数
    
        //01背包一维优化
        for(int i = 1;i <= n ;i ++)
            for(int j = m ;j >= v[i];j --)
                f[j] = max(f[j],f[j-v[i]] + w[i]);
    
        cout << f[m] << endl;
        return 0;
    }
    

4.分组背包

题目描述:

有 N 组物品和一个容量是 V 的背包。

每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 v i j v_{ij} vij ,价值是 w i j w_{ij} wij ,其中 i 是组号,j 是组内编号。

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

输出最大价值。

(1)问题分析
在这里插入图片描述

(2)代码实现

①基础版——二维

  • 思路看上图

    代码如下:

    #include<bits/stdc++.h>
    using namespace std;
    
    const int N=110;
    int f[N][N];  
    int v[N][N],w[N][N],s[N];  
    int n,m,k;
    
    int main(){
        cin>>n>>m;
        for(int i=1;i<=n;i++)
        {
            cin>>s[i];
            for(int j=0;j<s[i];j++)
            {
                cin>>v[i][j]>>w[i][j];  
            }
        }
    
        for(int i=1;i<=n;i++)
        {
            for(int j=0;j<=m;j++)
            {
                f[i][j]=f[i-1][j];  //不选
                for(int k=0;k<s[i];k++)
                {
                    if(j>=v[i][k])     
                        f[i][j]=max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);  //01背包
                }
            }
        }
        cout<<f[n][m]<<endl;
    }
    
    

②优化版—一维

  • 含有01背包,进行一维优化

    代码如下:

    #include<bits/stdc++.h>
    using namespace std;
    
    const int N=110;
    
    //使用的上一层的f,从大到小枚举体积,使用本层,从小到大枚举体积
    //使用上一层,保证我们算这个所用到的体积,还没有被计算过,所以是存的上一层的状态
    
    int n,m;
    int v[N][N],w[N][N],s[N];
    int f[N];
    
    int main()
    {
        cin>>n>>m;
    
        for(int i=1;i<=n;i++)
        {
            cin>>s[i];
            for(int j=0;j<s[i];j++)
                cin>>v[i][j]>>w[i][j];
        }
    
        for(int i=1;i<=n;i++)
            for(int j=m;j>=0;j--) //这里,使用的上一层,从大到小枚举
                for(int k=0;k<s[i];k++)
                    if(v[i][k]<=j)
                        f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
    
        cout<<f[m]<<endl;
    
        return 0;
    }
    

Alt

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

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

相关文章

[SUCTF 2018]GetShell

有个文件上传&#xff0c;给了部分源码 if($contentsfile_get_contents($_FILES["file"]["tmp_name"])){$datasubstr($contents,5);foreach ($black_char as $b) {if (stripos($data, $b) ! false){die("illegal char");}} } 可以知道有…

黑白照片如何变彩色?黑白照变彩色的秘诀分享。​

黑白照片如何变彩色&#xff1f;将黑白照片变成彩色可以给照片增添生动的视觉效果和真实感&#xff0c;使得人物、场景更加具体形象&#xff0c;让人们更容易与之产生共鸣和情感联系&#xff0c;此外&#xff0c;通过给黑白老照片添加颜色&#xff0c;还可以打破时间和空间的限…

社区分享|JumpServer引领我走向开源天地

编者注&#xff1a;以下内容基于山东青岛的JumpServer社区用户JonnyJ的社区分享整理而成。 “接触到JumpServer之后&#xff0c;我从一个开源受益者逐渐成长为开源的贡献者。其实我们每个人都可以成为开源贡献者&#xff0c;不局限于软件产品&#xff0c;哪怕只是你的一段共享…

K8S集群+kubeadm+flannel+docker+harbor实例

目录 第一章.环境准备 1.1.部署架构图 1.2.节点要求 1.3.部署软件 1.4.修改主机名 1.5.所有节点修改hosts文件 1.6.关闭防火墙规则&#xff0c;关闭selinux&#xff0c;关闭swap交换 1.7.调整内核参数 第二章.部署K8S集群 2.1.所有节点安装docker 2.2.所有节点安装ku…

KVM管理-快照

KVM管理-快照 创建快照 为虚拟机vm1创建一个快照 [rootmyserver ~]# virsh snapshot-create-as vm1 vm1.snap Domain snapshot vm1.snap created快照只能使用qcow2创建&#xff0c;raw格式一般无法创建快照 查看磁盘镜像信息 [rootmyserver ~]# qemu-img info /var/lib/lib…

方案设计——食物测温仪方案

食物测温仪&#xff0c;在食物烹饪时&#xff0c;温度和时间至关重要&#xff0c;所以食物测温仪孕育而生&#xff0c;当用户使用时只需将食物测温仪的探头插入食物中&#xff0c;即刻能得到当前食物温度数据&#xff0c;不必用经验判断。做为一款食物测温仪&#xff0c;运用场…

Spring Boot :统一功能处理

在用户登陆验证的业务中&#xff0c;如果只是使用Spring AOP的话&#xff0c;session无法获取的&#xff0c;还有各种参数&#xff08;request等&#xff09;很难获取&#xff0c;这时候Spring拦截器就发挥了重大的作用了。 1.Spring 拦截器 创建拦截器分俩步&#xff1a;1.创…

项目集效益管理

项目集效益管理是定义、创建、最大化和交付项目集所提供的效益的绩效领域。 本章内容包括&#xff1a; 1 效益识别 2 效益分析和规划 3 效益交付 4 效益移交 5 效益维持 项目集效益管理包括一系列对项目集的成功极为重要的要素。项目集效益管理包括阐明项目集的 计划效益和预期…

AMBER分子动力学模拟之结果分析(最低能量结果)-- HIV蛋白酶-抑制剂复合物(3)

AMBER分子动力学模拟之结果分析(最低能量结果)-- HIV蛋白酶-抑制剂复合物(3) 在analysis目录下 解析.out文件 下载process_mdout.perl 脚本 perl process_mdout.perl ../md/md0.out ../md/md1.out ../md/md2.out # 可以不使用md0.out # 或者 $AMBERHOME/bin/process_md…

ShardingSphere 5.3 系列ShardingSphere-Proxy保姆级教程 | Spring Cloud 50

一、前言 通过以下系列章节&#xff1a; Spring Boot集成ShardingSphere实现数据分片&#xff08;一&#xff09; | Spring Cloud 40 Spring Boot集成ShardingSphere实现数据分片&#xff08;二&#xff09; | Spring Cloud 41 Spring Boot集成ShardingSphere实现数据分片&…

Linux:centos:组账户管理 》》添加组,用户加入组(设置组密码),删除组,查询账户信息,查询登录用户信息

/etc/group # 组信息文件 /etc/gshadow # 组密码文件&#xff08;不常用&#xff09; groupadd &#xff08;属性&#xff09; 组名 # 新建组 groupdel &#xff08;属性&#xff09; 组名 # 删除组 gpasswd # 可以…

Cartographer源码阅读---番外篇: Submap封装与维护

Cartographer中Submap(子图)没有被直接的调用进行维护, 而是针对2D和3D场景分别派生出子类Submap2D和Submap3D, 进行调用. 以2D为例, 为了方便维护, 又把Submap2D封装成了ActiveSubmaps2D进行维护, 其维护方式类似与滑窗, 也是只维护最近的一些数据. 1. Submap类 /*** brief …

Python学习之生成带logo背景图的二维码(静态和动态图)

前言 二维码简称 QR Code&#xff08;Quick Response Code&#xff09;&#xff0c;学名为快速响应矩阵码&#xff0c;是二维条码的一种&#xff0c;由日本的 Denso Wave 公司于 1994 年发明。现随着智能手机的普及&#xff0c;已广泛应用于平常生活中&#xff0c;例如商品信息…

探索三维世界【4】:Three.js dat.gui gsap 的使用

探索三维世界【4】&#xff1a;Three.js & dat.gui & gsap 的使用 1、dat.gui是什么&#xff1f;2、gsap的介绍与使用2.1、前提准备工作&#xff08;绘制一个BoxGeometry&#xff09;2.2、安装引入gsap动画库2.3、使用gsap动画2.4、配合事件使用 3、使用dat.gui3.1、添…

生物信息学知识点

生物信息学知识点 1. 序列比对&#xff1a;1.1 基本概念&#xff1a;1.2 全局比对和局部比对&#xff1a;1.3 空位罚分的改进&#xff1a;1.4 同源性和相似性&#xff1a;1.5 相似性矩阵&#xff1a;1.5.1 PAM&#xff1a;1.5.2 BLOSUM&#xff1a; 2. BLAST算法&#xff1a;2.…

React | React的过渡动画

✨ 个人主页&#xff1a;CoderHing &#x1f5a5;️ React.js专栏&#xff1a;React的过渡动画 &#x1f64b;‍♂️ 个人简介&#xff1a;一个不甘平庸的平凡人&#x1f36c; &#x1f4ab; 系列专栏&#xff1a;吊打面试官系列 16天学会Vue 11天学会React Node专栏 &#…

Grafana之Clock Panel使用(06)

Clock Panel可以用来显示当前(各国)时间或用于倒计时,并支持每秒更新一次。 Clock plugin for Grafana | Grafana Labs Clock Panel也是Grafana Labs提供,但并非Native,需自行安装,安装命令如下: # grafana-cli plugins install grafana-clock-panel # systemctl …

前端面试题 — — vue篇

前端面试笔记之vue篇 前言1.数据双向绑定原理⭐⭐⭐2. VUE生命周期⭐⭐⭐3.组件之间如何传值⭐⭐⭐4.路由之间如何传参⭐5.谈一谈VUEX⭐⭐6.如何解决VUEX页面刷新数据丢失问题&#xff1f;⭐⭐7.computed和watch的区别&#xff1f;⭐⭐⭐8.如何封装axios&#xff1f;⭐9.Route和…

APP和小程序共同塑造现代化政务服务

随着移动互联网的飞速发展&#xff0c;政务服务也开始向移动端转移&#xff0c;政务App和小程序结合&#xff0c;可以使政府更好地实现数字化转型和提供优质的政务服务。本文将探讨政务App和小程序的结合优势&#xff0c;以及如何推进政务App和小程序的发展。 移动政务服务应用…