数据结构与算法学习笔记(Acwing提高课)----动态规划·背包模型(一)
数据结构与算法学习笔记----动态规划·背包模型(一)
@@ author: 明月清了个风
@@ first publish time: 2025.5.1ps⭐️背包模型是动态规划中的重要模型,基础课中已对背包模型的几种模版题有了讲解,[链接在这](数据结构与算法学习笔记----背包问题_有 n 件物品和一个体积为 m 的背包。第 i 个物品的体积为 vi,价值为 wi。每件物品-CSDN博客),
⭐️题目非常多,计划大概写两到三篇,建议首先复习一下基础课中对应的背包模型。这次是按照题目顺序写的,没有按照视频讲的顺序,因为刚开始讲的就是一系列多重背包问题,相对比较难,因此没有按照这个顺序。并且按照对应的背包模型进行了题目的分类,没有完全按照原来的顺序。
这一篇中主要是01背包问题和二维背包问题的应用题。
很多题目的代码都可以进行优化,有些题目给出了优化前后的代码,有些比较简单直接给了优化后的如二维背包问题的优化,建议可以先尝试写无优化的再看优化后的代码。
Acwing 423. 采药
辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。
为此,他想拜附近最有威望的医师为师。
医师为了判断他的资质,给他出了一个难题。
医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
如果你是辰辰,你能完成这个任务吗?
输入格式
输入文件的第一行有两个整数 T T T和 M M M,用一个空格隔开, T T T代表总共能够用来采药的事件, M M M代表山洞里的草药的数目。
接下来的 M M M行每行包括两个在 1 1 1到 100 100 100之间的整数,分别表示采摘某株草药的时间和这株草药的价值。
输出格式
输出文件包括一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。
数据范围
1 ≤ T ≤ 1000 1 \le T \le 1000 1≤T≤1000,
0 ≤ M ≤ 100 0 \le M \le 100 0≤M≤100
思路
这一题就是01背包问题的包装版本,思路完全一致,只是加入了具体场景的引用,算是本节内容的入门题了,就不具体讲了。可以用来复习01背包,下面给出了两个版本的代码,对应了无优化和有优化的版本,主要是对存储空间进行了优化,优化方法在基础课中已经详细讲过,这里就不讲了,那篇[链接在这](数据结构与算法学习笔记----背包问题_有 n 件物品和一个体积为 m 的背包。第 i 个物品的体积为 vi,价值为 wi。每件物品-CSDN博客)。
代码-无优化
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>using namespace std;const int N = 110, M = 1010;int n, m;
int v[N], w[N];
int f[N][M];int main()
{cin >> m >> n;for(int i = 1; i <= n; i ++) cin >> v[i] >> w[i];for(int i = 1; i <= n; i ++){for(int j = 1; 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;
}
代码-有优化
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>using namespace std;const int N = 110, M = 1010;int n, m;
int v[N], w[N];
int f[M];int main()
{cin >> m >> n;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;
}
Acwing 1024. 装箱问题
有一个箱子容量 V V V,同时有 n n n个物品,每个物品有一个体积(正整数)。
要求 n n n个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。
输入格式
第一行是一个整数 V V V,表示箱子容量。
第二行是一个整数 n n n,表示物品数。
接下来 n n n行,每行一个正整数,分别表示这 n n n个物品的各自体积。
输出格式
一个整数,表示箱子剩余空间。
数据范围
1 ≤ V ≤ 20000 1 \le V \le 20000 1≤V≤20000,
0 ≤ n ≤ 30 0 \le n \le 30 0≤n≤30
思路
这一题也是基本的01背包问题,为使剩余空间最小,就要拿体积做大的组合,虽然没有物品价值,但是可以将物品价值看成与该物品体积相同,这样就能转化为最基本的01背包问题了。
代码
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>using namespace std;const int N = 20010;int f[N];
int m, n;int main()
{cin >> m >> n;for(int i = 0; i < n; i ++){int v;cin >> v;for(int j = m; j >= v; j --) f[j] = max(f[j], f[j -v] + v);}cout << m - f[m] << endl;return 0;
}
Acwing 278. 数字组合
给定 N N N个正整数 A 1 , A 2 , ⋯ , A N A_1,A_2,\cdots,A_N A1,A2,⋯,AN,从中选出若干个数,使它们的和为 M M M,求有多少种方案。
输入格式
第一行包含两个整数 N N N和 M M M。
第二行包含 N N N个整数,表示 A 1 , A 2 , ⋯ , A N A_1,A_2,\cdots,A_N A1,A2,⋯,AN。
输出格式
包含一个整数,表示可选方案数。
数据范围
1 ≤ N < 100 1 \le N < 100 1≤N<100,
1 ≤ M < 10000 1 \le M < 10000 1≤M<10000
1 ≤ A i ≤ 1000 1 \le A_i \le 1000 1≤Ai≤1000
思路
同样是01背包的一个应用题,将所有数看成物品,数值看做体积即可,只不过要求的是体积正好为 M M M的方案数,对于状态表示使用f[i][j]
表示从前 i i i个数中选,且和为 j j j的方案,属性是方案数。
对于状态转移,根据第 i i i个数的选择情况即可。
需要注意的是,因为要求的是方案数,因此需要对f[0]
进行初始化。
代码
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>using namespace std;const int N = 10010;int n, m;
int f[N];int main()
{cin >> n >> m;f[0] = 1;for(int i = 1; i <= n; i ++){int v;cin >> v;for(int j = m; j >= v; j --) f[j] += f[j - v];}cout << f[m] << endl;return 0;
}
Acwing 8. 二维费用的背包问题
有 N N N件物品和一个容量为 V V V的背包,背包能承受的最大重量是 M M M。
每件物品只能用一次。体积是 v i v_i vi,重量是 m i m_i mi,价值是 w i w_i wi。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。
输入格式
第一行三个整数, N , V , M N,V,M N,V,M,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。
接下来 N N N行,每行三个整数 v i , m i , w i v_i,m_i,w_i vi,mi,wi,用空格隔开,分别表示第 i i i件物品的体积,重量和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0 < N < 1000 0 < N < 1000 0<N<1000,
0 < V , M < 100 0 < V,M < 100 0<V,M<100
0 < v i , m i < 100 0 < v_i,m_i < 100 0<vi,mi<100
0 < w i ≤ 1000 0 < w_i \le 1000 0<wi≤1000
思路
对于二维费用来说,仅仅是对一维01背包的扩展,在难度上其实并没有太大的提升,因为多了一维限制,因此状态表示也变成了三维,使用f[i][j][k]
表示所有从前 i i i个物品中选,并且总体积不超过 j j j,总重量不超过 k k k的选法。
对于状态划分,同样根据最后一个物品的选择进行划分,分为选物品 i i i和不选物品 i i i的两个部分,代码很简单,同样可以和01背包一样进行空间优化,这里直接给出了优化版本的代码,可以先尝试写无优化的版本再看这个。
代码
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>using namespace std;const int N = 1010;int n, V1, V2;
int f[N][N];int main()
{cin >> n >> V1 >> V2;for(int i = 1; i <= n; i ++){int v1,v2,w;cin >> v1 >> v2 >> w;for(int j = V1; j >= v1; j --)for(int k = V2; k >= v2; k --)f[j][k] = max(f[j][k], f[j - v1][k - v2] + w);}cout << f[V1][V2] << endl;return 0;
}
Acwing 1022. 宠物小精灵之收服
宠物小精灵是一部讲述小智和他的搭档皮卡丘一起冒险的故事。
一天,小智和皮卡丘来到了小精灵狩猎场,里面有很多珍贵的野生宠物小精灵。
小智也想收服其中的一些小精灵。然而,野生的小精灵并不那么容易被收服。
对于每一个野生小精灵而言,小智可能需要使用很多个精灵球才能收服它,而在收服过程中,野生小精灵也会对皮卡丘造成一定的伤害(从而减少皮卡丘的体力)。
当皮卡丘的体力小于等于0时,小智就必须结束狩猎(因为他需要给皮卡丘疗伤),而使得皮卡丘体力小于等于0的野生小精灵也不会被小智收服。当小智的精灵球用完时,狩猎也宣告结束。
我们假设小智遇到野生小精灵时有两个选择:收服它,或者离开它。如果小智选择了收服,那么一定会扔出能够收服该小精灵的精灵球,而皮卡丘也一定会受到相应的伤害;如果选择离开它,那么小智不会损失精灵球,皮卡丘也不会损失体力。
小智的目标有两个:主要目标是收服尽可能多的野生小精灵;如果可以收服的小精灵数量一样,小智希望皮卡丘受到的伤害越小(剩余体力越大),因为他们还要继续冒险。
现在已知小智的精灵球数量和皮卡丘的初始体力,已知每一个小精灵需要的用于收服的精灵球数目和它在被收服过程中会对皮卡丘造成的伤害数目。请问,小智该如何选择收服哪些小精灵以达到他的目标呢?
输入格式
输入数据的第一行包含三个整数: N N N, M M M, K K K,分别代表小智的精灵球数量、皮卡丘初始的体力值、野生小精灵的数量。
之后的 K K K行,每一行代表一个野生小精灵,包括两个整数:收服该小精灵需要的精灵球的数量,以及收服过程中对皮卡丘造成的伤害。
输出格式
输出为一行,包含两个整数 C , R C,R C,R,分别表示最多收服 C C C个小精灵,以及收服 C C C个小精灵时皮卡丘的剩余体力值最多为 R R R。
数据范围
0 < N < 1000 0 < N < 1000 0<N<1000,
0 < M < 500 0 < M < 500 0<M<500
0 < K < 100 0 < K < 100 0<K<100
思路
根据题意可以发现,一共有两个值有上限,也就是01背包问题中的体积,一个是精灵球的数量,另一个是皮卡丘的体力值,而要求的目标是野生小精灵的数量,因此,这一题为二维背包问题。
对于状态表示而言,使用f[i][j][k]
表示所有从前 i i i个物品中选择,且花费不超过 j j j和不超过 k k k的选法,也就是有两个限制,在这一题中就是从前 i i i个小精灵中选择要收服的小精灵,且精灵球数量不能超过 j j j,皮卡丘的体力值不能超过 k k k的方案,属性为最大值。
对于状态转移而言就和一维背包问题是一样的。需要注意的是本题的第二问要皮卡丘的剩余体力值最多,需要找到收服 C C C个小精灵时剩余的最多体力。
代码
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>using namespace std;const int N = 1010, M = 510;int n, m, k;
int f[N][M];int main()
{cin >> n >> m >> k;for(int i = 0; i < k; i ++){int v1, v2;cin >> v1 >> v2;for(int j = n; j >= v1; j --)for(int z = m - 1; z >= v2; z --)f[j][z] = max(f[j][z], f[j - v1][z - v2] + 1);}cout << f[n][m - 1] << endl;int x = m - 1;while(x > 0 && f[n][x - 1] == f[n][m - 1]) x --;cout << m - x << endl;return 0;}
Acwing 1020. 潜水员
潜水员为了潜水要使用特殊的装备。
他有一个带2种气体的气缸:一个为氧气,一个为氮气。让潜水员下潜的深度需要各种的数量的氧和氮。
潜水员有一定数量的气缸。
每个气缸都有重量和气体容量。
潜水员为了完成他的工作需要特定数量的氧和氮。
他完成工作所需气缸的总重的最低限度的是多少?
例如:潜水员有5个气缸。每行三个数字为:氧,氮的(升)量和气缸的重量:
3 36 120
10 25 129
5 50 250
1 45 130
4 20 119
如果潜水员需要5升的氧和60升的氮则总重最小为249(1,2或者4,5号气缸)。
你的任务就是计算潜水员为了完成他的工作需要的气缸的重量的最低值。
输入格式
第一行有 2 2 2个整数 m , n m,n m,n,他们表示氧、氮各自需要的量。
第二行为整数 k k k表示气缸的个数。
此后的 k k k行,每行包括 a i , b i , c i a_i,b_i,c_i ai,bi,ci, 3 3 3个整数,这些各自是:第 i i i个气缸里的氧和氮的容量及汽缸重量。
输出格式
仅一行包含一个整数,为潜水员完成工作所需的气缸的重量总和的最低值。
数据范围
1 ≤ m ≤ 21 1 \le m \le 21 1≤m≤21,
1 ≤ n ≤ 79 1 \le n \le 79 1≤n≤79
1 ≤ k ≤ 1000 1 \le k \le 1000 1≤k≤1000
1 ≤ a i ≤ 21 1 \le a_i \le 21 1≤ai≤21
1 ≤ b i ≤ 79 1 \le b_i \le 79 1≤bi≤79
1 ≤ c i ≤ 800 1 \le c_i \le 800 1≤ci≤800
思路
首先很容易看出这题有两个限制氧气和氮气,但是与上一题不同的是,对于这两维费用的要求是至少需要这么多,使重量最低,也就是和上面的题目是反过来的,因此也需要有一些变化。
对于背包问题的状态表示而言,体积的限制有三种:①体积最多是 j j j ②体积恰好是 j j j ③体积至少是 j j j。这一题就是第三种情况。
对于状态表示,仍然是使用f[i][j][k]
,但含义变为所有从前 i i i个气缸中选,并且氧气至少是 j j j,氮气至少是 k k k的选法,要求的属性是最小值。
对于状态划分,仍然是根据第 i i i个气缸的选择进行划分,也就是是否包含该气缸,所有不包含第 i i i个物品的所有选法也就是f[i - 1][j][k]
,包含第 i i i个物品的选择集合可以先将物品 i i i去掉,那么根据一般的表示就是f[i - 1][j - v1][k - v2]
,但是需要注意的是,在之前如果是这个式子,因为要求是体积恰好是 j j j和 k k k,因此我们会有一个判断就是 j ≥ v 1 j \ge v1 j≥v1且 k ≥ v 2 k \ge v2 k≥v2才能成立,但是在这里因为要求是至少有那么多,只要比它多就是成立的,因此对于 j j j和 k k k的限制就失效了,即使此时 j < v 1 j < v1 j<v1或者 k < v 2 k < v2 k<v2也可以,因为实际意义是至少要这么多氧气和氮气,因此它最低就是 0 0 0,表示什么都没选,而选择了物品 i i i之后就满足了氧气至少为 j j j,氮气至少为 k k k,也就是只要比 j j j和 k k k多就行,多多少都没事。
代码
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>using namespace std;const int N = 22, M = 80;int n, m, k;
int f[N][M];int main()
{cin >> n >> m >> k;memset(f, 0x3f, sizeof f);f[0][0] = 0;while(k --){int v1, v2, w;cin >> v1 >> v2 >> w;for(int j = n; j >= 0; j --)for(int z = m; z >= 0; z --)f[j][z] = min(f[j][z], f[max(0, j -v1)][max(0, z - v2)] + w);}cout << f[n][m] << endl;return 0;
}