完全背包问题详解

背包问题详解

2.完全背包

2.1引入

小明去文具店采购,他有 W 元预算,店里卖各种文具,每种的单价和实用价值都不一样,但每种文具库存无限,想买几个买几个。他该怎么买,才能让总实用价值最大?

物品价格:

笔记本:5元/本,价值8分

签字笔:3元/支,价值5分

橡皮:2元/块,价值3分

小明有20元预算,笔记本买4本最值吗?还是混着买?

只能选一次是01背包,而可以选多次就是完全背包


2.2完全背包与01背包的区别

01背包 完全背包
每件物品 只能选择一次 可以选择无限次
遍历顺序(二维) 正序 正序
遍历顺序(一维) 倒叙 正序

2.3为什么使用动态规划?

如果要解决完全背包问题,我们会有一些选择:

方法 思路 时间复杂度 评价
暴力枚举 枚举每种物品选几个 \(+∞种\) 不可行×
贪心 每次选"性价比"最高的 \(O(n log n)\) 不一定是最优×
转化为01背包 把无限拆成有限个 \(O(n × W × W/w)\) 太慢×
动态规划 记录子问题最优解 \(O(n × W)\) 最优√

2.4二维dp写法

2.4.1状态转移方程

\(dpij代表了只考虑前i种物品,容量为j时的最大价值\)

状态转移方程:

复制代码
dp[i][j]=max(dp[i-1][j],dp[i][j-w[i]]+v[i])
  • 选择第i种:dpi-1j
  • 不选第i种:dpij-w\[i]+vi

注:状态转移方程中wi表示第i样物品的重量,vi表示第i样物品的价值

2.4.2二维代码

复制代码
#include<bits/stdc++.h>//万能头文件
using namespace std;
int dp[1005][1005];
int main() {
    int m,n;cin>>n>>m;//n代表物品数量,m代表背包大小
    int w,c;
    for(int i=1;i<=n;i++) {
        cin>>w>>c;//w代表重量,c代表价值
        for(int j=0;j<=m;j++) {//枚举各个背包大小时的最大价值
            dp[i][j]=dp[i-1][j];//默认为上一次的结果
            if(j>=w) {//如果当前背包大小不少于w
                dp[i][j]=max(dp[i][j],dp[i][j-w]+c);//状态转移方程
            }
        }
    }
    cout<<dp[n][m];//输出答案
    return 0;
}

2.5一维空间优化

2.5.1如何压缩成一维

二维状态转移第\(i\)行用到了:

  • 上一行同列:\(dpi-1j\)

  • 当前行左侧:\(dpij - w\[i]\)

所以压缩成一维后,需要从左往右遍历,因为 \(dpj-w\[i]\)必须是当前行已经更新过的值。

2.5.2一维代码

复制代码
#include<bits/stdc++.h>//万能头文件
using namespace std;
int dp[1005];//定义数组
int main() {
    int m,n;cin>>n>>m;//
    int w,c;//w为重量,c为价值
    for(int i=1;i<=n;i++) {
        cin>>w>>c;//输入
        for(int j=0;j<=m;j++) {//正序遍历
            if(j>=w) {//判断
                dp[j]=max(dp[j],dp[j-w]+c);//取较大值
            }
        }
    }
    cout<<dp[m];//输出答案
    return 0;
}

2.6例题

2.6.1 P1616 疯狂的采药

题目背景

此题为纪念 LiYuxiang 而生。

题目描述

LiYuxiang 是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:"孩子,这个山洞里有一些不同种类的草药,采每一种都需要一些时间,每一种也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。"

如果你是 LiYuxiang,你能完成这个任务吗?

此题和原题的不同点:

\(1\). 每种草药可以无限制地疯狂采摘。

\(2\). 药的种类眼花缭乱,采药时间好长好长啊!师傅等得菊花都谢了!

输入格式

输入第一行有两个整数,分别代表总共能够用来采药的时间 \(t\) 和代表山洞里的草药的数目 \(m\)。

第 \(2\) 到第 \((m + 1)\) 行,每行两个整数,第 \((i + 1)\) 行的整数 \(a_i, b_i\) 分别表示采摘第 \(i\) 种草药的时间和该草药的价值。

输出格式

输出一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。

输入输出样例 #1
输入 #1
复制代码
70 3
71 100
69 1
1 2
输出 #1
复制代码
140
说明/提示

【数据规模与约定】

  • 对于 \(30\%\) 的数据,保证 \(m \le 10^3\)。
  • 对于 \(100\%\) 的数据,保证 \(1 \leq m \le 10^4\),\(1 \leq t \leq 10^7\),且 \(1 \leq m \times t \leq 10^7\),\(1 \leq a_i, b_i \leq 10^4\)。

2.6.2解法

  • 本题每种药都可以采无限次,所以我们可以想到完全背包
  • 再结合本题的数据规模,我们可以发现用二维空间会超限,所以本题需要用一维dp

2.6.3代码

复制代码
#include<bits/stdc++.h>//万能头文件
using namespace std;
#define ll long long
ll dp[10000005];//定义一维数组
int main() {
    ll m,n;cin>>m>>n;//输入
    ll w,c;
    for(ll i=1;i<=n;i++) {
        cin>>w>>c;//w为每种药材采摘时间,c为采摘总时间
        for(ll j=0;j<=m;j++) {//正向遍历
            if(j>=w) {//判断时间是否充足
                dp[j]=max(dp[j],dp[j-w]+c);
            }
        }
    }
    cout<<dp[m];//输出结果
    return 0;
}

2.7推荐练习题目

相关推荐
皓月斯语3 小时前
B3842 [GESP202306 三级] 春游 题解
数据结构·c++·算法·题解
萌动的小火苗4 小时前
嵌入式开发中的栈与队列:任务调度为什么依赖数据结构
数据结构·c++·单片机·嵌入式硬件
星恒随风4 小时前
C++ STL 栈详解:stack 的使用、经典题目与简单模拟实现
开发语言·数据结构·c++·笔记·学习
txzrxz4 小时前
单调队列讲解
数据结构·c++·算法·单调队列
WWTYYDS_6665 小时前
JsonCpp超详细使用教程
c++
Joey_friends5 小时前
指纹authenticate流程图
android·java·c++
小小晓.5 小时前
C++小白记:C风格字符串和数组用法
c语言·开发语言·c++
cpp_25016 小时前
P1540 [NOIP 2010 提高组] 机器翻译
数据结构·c++·算法·队列·noip·洛谷题解
咕白m6256 小时前
通过 C++ 写入数据到 Excel 文档
c++·后端