背包九讲
作者:一个梦想进入大厂的大学生
tips:以下描述都是基于题目而言, 给出思路与代码实现
一、01背包问题
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0 < N,V ≤ 1000
0 < vi,wi ≤ 1000
题目链接:acwing-2
大意: N个物品,每个物品价值为w[i],一个容积为V的背包,求出能装下货物的最大价值
- 集合:所有只考虑前i个物体,且总体积不大于j的所有选法
- 属性:Max
- 状态计算:状态转移方程,推导过程见下
计算
f[i][j]
: 借助f[i-1][j]
来进行递推,在考虑第 i 层的时候,我们有两种选择:含第 i 个物品和不含第 i 个物体
不含第i个物体的时候,显而易见有转移方程:f[i][j] = f[i - 1][j]
,
当含第 i 个物体的时候,我们先将第 i 个物体的位置留出来,也就是有转移方程:f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i])
于是我们得出了我们需要的转移方程,代码见下表
//朴素版
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
const int N = 1010;
int v[N], w[N], f[N][N];
using namespace std;
int main()
{
int N, V; cin >> N >> V;
for(int i = 1; i <= N; i ++) cin >> v[i] >> w[i];
for(int i = 1; i <= N; i ++)
for(int j = 0; j <= V; 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][V];
return 0;
}
不难发现,在进行递推的过程中,我们计算第 i 层的时候,只用到了 i - 1 层的数据,所以可以对数组经行空间压缩,将二维数组f[i][j]
压缩为一维f[j]
这里需要注意,当我们得出初始的状态转移方程之后,初始的状态转移方程可能需要很大的空间和时间,我们就需要将初始的状态转移方程进行等效变换,变得尽量使用较少的时间和空间
f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i])
当我们将第 i 维数直接去掉之后变为
f[j] = max(f[j], f[j - v[i]] + w[i])
其实是没有问题的,但是联系到代码后,如下
//朴素
for(int i = 1; i <= N; i ++)
for(int j = 0; j <= V; 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]);
}
//优化后
for(int i = 1; i <= N; i ++)
for(int j = V; j >= v[i]; j --)// <----------------
{
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
注意到代码里面箭头指的代码,这里的遍历顺序发生了改变,因为如果是从小到大遍历,f[j - v[i]]
会在本层先被更新,然后又去更新本层的其他f[j]
。能够压缩空间的本质是每一次的 f 数组都会被更新掉,比如更行到第 i 层的时候,i - 1层的数据就被替换掉,存储就没有意义,于是我们便直接去掉 i 这个维度。观察原转移方程可以发现,后面是第 i - 1 层的f[i - 1][j - v[i]]
,所以我们更新的时候只能使用上一层的数据来更新本层数据,将其变为从大到小枚举之后,f[j] 一定会在f[j - v[i]]之前被更新掉,换言之就是使用的是第 i -1 层的数据更新的 f[j],就避免了问题的发生,故优化之后的代码为:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
const int N = 1010;
int v[N], w[N], f[N];
using namespace std;
int main()
{
int N, V; cin >> N >> V;
for(int i = 1; i <= N; i ++) cin >> v[i] >> w[i];
for(int i = 1; i <= N; i ++)
for(int j = V; j >= v[i]; j --)
{
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
cout << f[V];
return 0;
}
二、完全背包问题
有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
题目链接:acwing-3
大意:N种物品(无限个),每个物品价值为w[i],一个容积为V的背包,求出能装下货物的最大价值
- 集合:所有只考虑前 i 种物品,且总体积不大于 j 的所有选法
- 属性:Max
- 状态计算:状态转移方程,推导如下
计算f[i][j]
时, 可以借助f[i - 1][j]
来递推出结果,思维和01背包相似,有下列情况:
- 含第 i 种物品 0 个
- 含第 i 种物品 1 个
- 含第 i 种物品 2 个
- ……
- 含第 i 种物品 k 个
故可以得出转移方程:
含第i种物品0个:f[i][j] = f[i - 1][j]
含第i种物品1个:f[i][j] = f[i - 1][j - 1*v[i]] + 1*w[i]
……
含第i个物体k个:f[i][j] = f[i - 1][j - k*v[i]] + k*w[i]
取上述所有值的最大值即可
所以容易思考出代码的主要结构,三层循环嵌套即可,代码见下表:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
const int N = 1010;
int v[N], w[N];
int f[N][N];
int main()
{
int n, m; 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 ++)
{
//f[i][j] = f[i - 1][j];
if(j >= k * v[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;
}
明显上述代码的时间复杂度为三次方级别:O(nmk),显然是不满足最优解的。故得出以下将代码优化为两维转移方式:
f[i][j] = max(f[i-1][j], f[i-1][j-v[i]]+w[i], f[i-1][j-2*v[i]] + 2*w[i],……)
f[i][j-v[i] = max(.......f[i-1][j-v[i]], .... f[i-1][j-2*v[i]] + 1*w[i],……)
很容易发现,两组数据很相似,且因为第二项的j比第一项的j少v[i],所以Max里面的项数也会少一项,所有后面是完美对其的,唯一不同的就是每个数据少了一个w[i],但是对应取最大值不影响,最后加上去即可,最后两式结合得到二维优化的转移方程:
先f[i][j] = f[i-1][j]
,后f[i][j] = Max(f[i][j], f[i][j-v[i]] + w[i])
,代码可以简化为:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
const int N = 1010;
int v[N], w[N];
int f[N][N];
int main()
{
int n, m; 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 ++)
{
f[i][j] = f[i - 1][j];
if(j >= v[i])
f[i][j] = max(f[i - 1][j], f[i][j - v[i]] + w[i]);
}
cout << f[n][m] << endl;
return 0;
}
上述代码的时间复杂度是n2级别,但是可以结合01背包的方式,发现该题目仍然可以从空间的角度优化,比较01背包和完全背包的递推式:
- 01背包:
f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i])
- 完全背包:
f[i][j] = max(f[i][j], f[i][j - v[i]] + w[i])
不同点在于完全背包是基于第 i 层,而01背包是基于第 i - 1层,所以这里就不必担心是否本层本更新的问题了,反之如果像01背包一样的逆序遍历,反倒会使用 i - 1来更新 i 层的数据,故这里必须使用升序,代码如下
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
const int N = 1010;
int v[N], w[N];
int f[N];
int main()
{
int n, m; 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;
}
三、多重背包问题
有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N≤1000
0<V≤2000
0<vi,wi,si≤2000
题目链接:acwing-5
- 集合:所有只从第 i 个物品中选,且总体积不超过 j 的选法
- 属性:Max
- 状态计算:状态转移方程,证明如下
多重背包问题和完全背包问题极其相似,不同的是完全背包每种物品的数量是无限的,而多重背包每种物品的数量是有s[i]限制的,依然可以使用f[i - 1][j]
来递推出f[i][j]
- 含第 i 种物品 0 个
- 含第 i 种物品 1 个
- 含第 i 种物品 2 个
- ……
- 含第 i 种物品 s[i] 个
故和完全背包的朴素做法相似即可,代码如下表
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 110;
int s[N], v[N], w[N];
int f[N][N];
int main()
{
int n, m; 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(nms[i]),显然是有优化空间的,当我们尝试使用完全背包的二维优化方式时,如下
完全背包问题:
f[i][j] = max(f[i-1][j], f[i-1][j-v[i]]+w[i], f[i-1][j-2*v[i]] + 2*w[i],……)
f[i][j-v[i] = max(.......f[i-1][j-v[i]], .... f[i-1][j-2*v[i]] + 1*w[i],……)
多重背包问题:
f[i][j] = max(f[i-1][j], f[i-1][j-v[i]]+w[i], f[i-1][j-2*v[i]] + 2*w[i],……)
f[i][j-v[i]] = max(.......f[i-1][j-v[i]], .... f[i-1][j-2*v[i]] + 1*w[i],……, x)
不难发现,两者的项数是一致的,因为对于第 i 层来讲,一定有放0~s[i]这s[i] + 1种方案,前提是j - v[i] >= s[i]*v[i], 那么必然是有数据能够满足这些条件的。 而为什么完全背包会相差一项呢,因为完全背包物品个数是无限的,那么必然f[i][j]
能比f[i][j-v[i]]
多考虑一种情况,大一个物品的空间,则上线可以多方一个,故可以将两个式子合并得出一个极简的状态转移方程,但是本题目项数是一致的,所以f[i][j-v[i]]
的最后一项f[i][j]
没有,所以不能合并,于是该转移方式不能有效的优化代码,下面给出一个新的优化方式
“二进制打包优化法”
比如每个产品的s[i]数量是67,我们可以将该67个一样的产品进行打包分组,使得从打包后的若干组里面进行选择一定能凑出1~67中间的任意一个数,再使用01背包的方式进行处理,下面给出67的分组样例
利用的分组数 | 可以表示的范围 |
---|---|
1 | 1 |
1、2 | 1~3 |
1、2、4 | 1~7 |
1、2、4、8 | 1~15 |
1、2、4 、8、16 | 1~31 |
1、2、4 、8、16 、32 | 1~63 |
1、2、4 、8、16 、32、4 | 1~67 |
tips:“可以表示的范围”是用“利用的分组数”组合相加得到的
为什么会出现这样的想法,联系二进制实际存储的方式可以知道,一个数一定有一个自己的二进制表示方式,而我们是将其二进制表示方式里面的不同位的数拆开,比如
- 20 = 1
- 21 = 2
- 22 = 14
- ……
- 2n = …
当我们将某个数拆成这样的若干个数之后,会出现拆下去的情况,这时候我们只需要将多余的数拿出来,单独打包即可比如上面67最后的4无法再拆解,直接将4进行打包,则凭借所有的包组合出1~67里面的数据,下面给出代码实现
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
//数据范围是2000,拆成二进制有11次方,故需要22000,这里开25000,但是开了25000就一定要用优化版的01背包问题
const int N = 25000;
int s[N], v[N], w[N];
int f[N];
int main()
{
int n, m; cin >> n >> m;
int cnt = 0;
for(int i = 1; i <= n; i ++)
{
//将每种物体拆分开形成能组合成任意小于等于本身组数的集合v w s
int a, b, c; cin >> a >> b >> c;
int mul = 1;
while(c > mul)
{
cnt ++;
v[cnt] = mul * a;//这里是将物品打包好之后再放入数组
w[cnt] = mul * b;
c -= mul;
mul *= 2;
}
if(c > 0)//最后可能会多出一部分没有办法继续分解,直接打包加入数组
{
cnt ++;
v[cnt] = c * a;
w[cnt] = c * b;
}
}
//01背包模板
for(int i = 1; i <= cnt; 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;
}
四、分组背包问题
有 N 组物品和一个容量是 V 的背包。
每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。
接下来有 N 组数据:
每组数据第一行有一个整数 Si,表示第 i 个物品组的物品数量;
每组数据接下来有 Si 行,每行有两个整数 vij,wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤100
0<Si≤100
0<vij,wij≤100
题目链接:acwing-9
- 集合:只从前 i 个组里面选,且总体积不超过j
- 属性:Max
- 状态计算:状态转移方程,证明如下:
每组最多只能选择一个,这样的依然可以从 i - 1 的维度推出 i 维度,转移方程如下
f[i][j] = max(f[i-1][j], f[i-1][j-v[i][k]] + w[i][k], ……)
这里的 k 是指每一组里面的每一种物品,只能选择一个,就在 i 组里面挑一个,选出最优,在存储的时候我们用 s 数组来存储每组里面物品的个数,便于遍历,我们只需要在01背包问题的基础上加一个维度来遍历每一组里面的所有物品即可,这里是三重循环,因为每组里面的物品价值不一样, 数量不一样,所以必须要遍历到每一个物品,这里没有办法像完全背包一样找递推关系优化到二维时间,朴素版代码如下:
代码如下:
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 110;
int v[N][N], w[N][N], s[N];
int f[N][N];
int main()
{
int n, m; cin >> n >> m;
for(int i = 1; i <= n; i ++)
{
cin >> s[i];
//第i类物品,有k个
for(int j = 1; 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 = 1; 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]);
}
}
}
cout << f[n][m] << endl;
return 0;
}
仔细发现,这里在第 i 层也只用到 i - 1 层的数据,所以可以像01背包一样逆向枚举 j 即可,空间优化版代码如下:
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 110;
int v[N][N], w[N][N], s[N];
int f[N];
int main()
{
int n, m; cin >> n >> m;
for(int i = 1; i <= n; i ++)
{
cin >> s[i];
//第i类物品,有k个
for(int j = 1; 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 = 1; k <= s[i]; k ++)
{
if(j >= v[i][k]) f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
}
}
}
cout << f[m] << endl;
return 0;
}
代码注释:这里与其他背包问题不同,其他背包问题的每组里面的价值已知,所以只需要从w[i] ~ m,因为即使遍历了小于w[i],也会被if给划掉,
f[i - w[i]]
的下标不能小于0 ;
这里每个价值不一样,可能为0,可能为1,没有确定的 " w[i] " 所以不确定到底下标什么时候小于0,所有统一从m~0, 然后给出if判断
五、混合背包问题
有 N 种物品和一个容量是 V 的背包。
物品一共有三类:
第一类物品只能用1次(01背包);
第二类物品可以用无限次(完全背包);
第三类物品最多只能用 si 次(多重背包);
每种体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
si=−1 表示第 i 种物品只能用1次;
si=0 表示第 i 种物品可以用无限次;
si>0 表示第 i 种物品可以使用 si 次;
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
−1≤si≤1000
题目链接:acwing-7
- 集合:只从前 i 个组里面选,且总体积不超过j
- 属性:Max
- 状态计算:状态转移方程推导如下
由题意知,有三种物品:
- 最多使用一次(01背包问题)
- 最多使用s[i]次(多重背包问题)
- 最多使用无线次(完全背包问题)
于是当我们思考这个问题的时候,可以直接递推加上三重判断分类来分析问题,对于每个物品来说,都需要判断它所属的是哪一种背包问题,然后用对应的状态转移方程来得到当前物品的最优策略,但是我们不难思考到,当我们对其中的多重背包问题进行决策的时候,我们采取的方法是将其进行二进制分组优化,转化为01背包问题来解决,但是当我们只讨论多重背包问题的时候,我们会遇到一个瓶颈,二进制分组优化之后的物品数量变多,会变得相当麻烦,所以我们可以在读入给时候就将多重背包问题拆分后读入数组,这样三种背包问题就只剩下两种,为01背包问题和完全背包问题。当我们数据拆分读取完毕之后,开始递推的时候,因为我们对于每个物品来讲都是独立的,基于之前所有最优决策的,所以仍然可以采用 i 层或 i-1 层来得到 i 层,这也就是为什么可以在最底层循环直接分类递推的原因。 代码如下:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector>
using namespace std;
//范围是1000,换成二进制是10,也就是十倍,至少需要10000,这里开15000
const int N = 15000;
struct Things{
int s;
int v, w;
};
int f[N];
vector<Things> things;
int main()
{
int n, m; cin >> n >> m;
for(int i = 1; i <= n; i ++)
{
int a, b, c; cin >> a >> b >> c;
if(c < 0) things.push_back({-1, a, b});
else if(c == 0) things.push_back({0, a, b});
else
{
//这里是多重背包,需要分组存放
int k = 1;
while(c > k)
{
things.push_back({-1, k * a, k * b});
c -= k;
k *= 2;
}
if(c > 0) things.push_back({-1, c * a, c * b});
}
}
//现在来分情况讨论,有的是01背包问题,有的是完全背包问题,分两组讨论
for(auto thing : things)
{
if(thing.s == -1)//01背包问题
{
for(int j = m; j >= thing.v; j --) f[j] = max(f[j], f[j - thing.v] + thing.w);
}
else//完全背包问题
{
for(int j = thing.v; j <= m; j ++) f[j] = max(f[j], f[j - thing.v] + thing.w);
}
}
cout << f[m] << endl;
return 0;
}
但是我们仔细发现可知,所谓的完全背包问题也受到一个条件所限制,虽然是无限个,但是是取不到无限个的,因为背包体积上限不允许也不可能取到当前物品的无限个,所以我们可以将其无限个改为背包容积所允许的上限个,这也就将完全背包问题转化为多重背包问题,根据上文可知,多重背包问题的一个优化方式是二进制分组优化,这样分组之后其实是将一个多重背包问题转化为01背包问题,故我们在读入数据的时候可以将三种背包问题全部转化为01背包问题,代码实现如下:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector>
using namespace std;
//范围是1000,换成二进制是10,也就是十倍,至少需要10000,这里开15000
const int N = 15000;
struct Things{
int s;
int v, w;
};
int f[N];
vector<Things> things;
int main()
{
int n, m; cin >> n >> m;
for(int i = 1; i <= n; i ++)
{
int a, b, c; cin >> a >> b >> c;
if(c < 0) things.push_back({-1, a, b});
else//这里是完全背包问题和多重背包问题,但是因为背包容积优先,所以可以转化为多重背包问题
{
if(c == 0) c = m / a;//如果是完全背包问题,那么按照最多能存储的数量来存
//这里是多重背包,需要分组存放
int k = 1;
while(c > k)
{
things.push_back({-1, k * a, k * b});
c -= k;
k *= 2;
}
if(c > 0) things.push_back({-1, c * a, c * b});
}
}
//现在全部都是01背包问题,直接用01背包问题的状态转移方程
for(auto thing : things)
if(thing.s == -1)//01背包问题
for(int j = m; j >= thing.v; j --) f[j] = max(f[j], f[j - thing.v] + thing.w);
cout << f[m] << endl;
return 0;
}
六、二维费用的背包问题
有 N 件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。
每件物品只能用一次。体积是 vi,重量是 mi,价值是 wi。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,M,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。
接下来有 N 行,每行三个整数 vi,mi,wi,用空格隔开,分别表示第 i 件物品的体积、重量和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N≤1000
0<V,M≤100
0<vi,mi≤100
0<wi≤1000
题目链接:acwing-8
- 集合:只从前 i 个组里面选,且总体积不超过j
- 属性:Max
- 状态计算:状态转移方程推导如下:
二维费用的背包问题实质上和01背包问题很像,就是多了一个维度,朴素版的01背包问题是二维空间,故朴素版的二维费用的背包问题是三维空间,但是不难发现,这里也是利用 i - 1层来递推得到i层,故也可以将空间复杂度优化,代码和01背包问题极度相似,值得注意的是需要对应清楚哪一维度代表的是什么意思,比如我的代码 f 数组第一维是表示体积,第二维是表示重量,在后面递推的时候需要三层循环遍历,体积和重量都需要全部枚举一遍,代码如下:
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 1010;
int V[N], W[N], money[N];
int f[N][N];//左边是体积,右边是重量
int main()
{
int n, m, w; cin >> n >> m >> w;//件数,容积,最大重量
for(int i = 1; i <= n; i ++) cin >> V[i] >> W[i] >> money[i];
for(int i = 1; i <= n; i ++)
for(int j = m; j >= V[i]; j --)//容积
for(int k = w; k >= W[i]; k --)//重量
f[j][k] = max(f[j][k], f[j - V[i]][k - W[i]] + money[i]);
cout << f[m][w] << endl;
return 0;
}
更新尚未完全,仍在学习中,欢迎批评指正!
qq:2166364824