Acwing动态规划——完全背包问题
问题的描述
完全背包问题
有
N
N
N种物品和一个容量是
V
V
V的背包,每种物品都有无限件可用。
第
i
i
i种物品的体积是
v
i
v_i
vi,价值是
w
i
w_i
wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
数据具体内容
输入格式
第一行两个整数,
N
N
N,
V
V
V,用空格隔开,分别表示物品种数和背包容积。
接下来有
N
N
N行,每行两个整数
v
i
,
w
i
v_i,w_i
vi,wi,用空格隔开,分别表示第
i
i
i种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0
<
N
,
V
≤
1000
0<N,V≤1000
0<N,V≤1000
0
<
v
i
,
w
i
≤
1000
0<vi_,w_i≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10
动态规划的朴素做法
代码
//三维代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int m, n;
int v[N], w[N];
int f[N][N];
int main()
{
// get inputting
cin >> n >> m;
for(int i = 1; i <= n; i++)
{
cin >> v[i] >> w[i];
}
// Dynamic programming
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] = max(f[i][j], f[i-1][j - k * v[i]] + k * w[i]);
}
}
}
// output
cout << f[n][m] << endl;
return 0;
}
解题逻辑梳理
- 同01背包问题不同的是,完全背包问题中每个物品的数量是不限的,因此对物品 i i i,想拿多少就拿多少。因此不能将 f ( i , j ) 和 f ( i − 1 , j ) f(i,j) 和 f(i-1, j) f(i,j)和f(i−1,j)之间的关系简单的分为,拿第 i i i件物品和不拿第 i i i件物品了,而是要对拿 0 − n 0-n 0−n件物品 i i i都逐一进行分析。
- f ( i , j ) f(i,j) f(i,j)依旧代表在有 i i i种物品,背包空间为 j j j的情况下,所有的可行解的物品总价值的集合。
- 按照01背包问题的情况分类规则,对完全背包问题也采用相同的分析方法。
- 只考虑是否拿第 i i i个物品的情况,拿了不同个数的 i i i种类物品,关系式应该如何表示。其情况可以分为,分别拿了 i i i种物品 0 , 1 , 2 , . . . , n 0,1,2,...,n 0,1,2,...,n件进入背包。那么就有关系式 f ( i , j ) = { f ( i − 1 , j − k ∗ v [ i ] ) + k ∗ w [ i ] ∣ k = 0 , 1 , 2 , . . . , n } f(i, j) = \{f(i-1, j-k*v[i]) +k*w[i]|k=0,1,2,...,n\} f(i,j)={f(i−1,j−k∗v[i])+k∗w[i]∣k=0,1,2,...,n}
- 由于每种物品都是无限多个,最少情况下是拿 0 0 0个,最多则可以一直拿,拿到背包装不下为止,因此拿取物品的个数 k k k(整数)必须满足 k > = 0 k >= 0 k>=0和 j > = k ∗ v [ i ] j >= k * v[i] j>=k∗v[i]两个条件。
- f ( i , j ) f(i, j) f(i,j)是所有可行方案的集合,题中所求的是具有最大总价值的选择方法的最大总价值是多少,即 m a x ( f ( i , j ) ) max(f(i,j)) max(f(i,j))。
二维动态规划
代码
//二维解答
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int m, n;
int v[N], w[N];
int f[N][N];
int main()
{
// get inputting
cin >> n >> m;
for(int i = 1; i <= n; i++)
{
cin >> v[i] >> w[i];
}
// Dynamic programming
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]);
}
}
// output
cout << f[n][m] << endl;
return 0;
}
解题逻辑梳理
- 二维的代码是在朴素做法的基础上得到的,因此基本思想还是朴素做法不变,只是在朴素做法的基础上作等价代换,使得表示上更加简单。
- 在朴素做法中,有关系式
f ( i , j ) = { f ( i − 1 , j − k ∗ v [ i ] ) + k ∗ w [ i ] ∣ k = 0 , 1 , 2 , . . . , n } f(i, j) = \{f(i-1, j-k*v[i]) +k*w[i]~|~k=0,1,2,...,n\} f(i,j)={f(i−1,j−k∗v[i])+k∗w[i] ∣ k=0,1,2,...,n},因此可以推导出 f ( i , j − v [ i ] ) = { f ( i − 1 , j − k ∗ v [ i ] ) + ( k − 1 ) ∗ w [ i ] ∣ k = 1 , 2 , 3 , . . . , n } f(i, j-v[i]) = \{f(i-1, j-k*v[i]) + (k-1)*w[i]~|~k = 1, 2, 3, ..., n\} f(i,j−v[i])={f(i−1,j−k∗v[i])+(k−1)∗w[i] ∣ k=1,2,3,...,n}。
那么可以看出 f ( i , j ) f(i,j) f(i,j)除了 k = 0 k=0 k=0时候的表达式,剩下的 k = 1 , 2 , 3 , . . . , n k=1,2,3,...,n k=1,2,3,...,n时候的余项等于 f ( i , j − v [ i ] ) + w [ i ] f(i,j-v[i]) + w[i] f(i,j−v[i])+w[i]
因此我们就得到了一套新的关系式,即:
f ( i , j ) = { f ( i − 1 , j ) , f ( i , j − v [ i ] ) + w [ i ] } f(i,j) = \{f(i-1,j), f(i, j - v[i]) +w[i]\} f(i,j)={f(i−1,j),f(i,j−v[i])+w[i]} - 可以看到在这个过程中,k消失了,因此就将k的循环删去,成功实现了降维。但是,我们可以明显发现,在降维的公式中, j j j必须大于 v [ i ] v[i] v[i]的时候,二维关系式才成立。因此需要对 j j j在小于和大于 v [ i ] v[i] v[i]时候分情况讨论。
一维动态规划
代码
//一个维修改
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int m, n;
int v[N], w[N];
int f[N];
int main()
{
// get inputting
cin >> n >> m;
for(int i = 1; i <= n; i++)
{
cin >> v[i] >> w[i];
}
// Dynamic programming
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]);
}
}
// output
cout << f[m] << endl;
return 0;
}
解题逻辑梳理
- 从二维到一维的过程实际和01背包问题从二维向一维转换有着异曲同工之妙,但是也有一定的差异。二维到一维的优化也是建立在等价变换的基础上的。
- 首先尝试对代码减少一个维度,因为同01背包问题类似,在考虑完全背包问题时,也是只考虑了对前 i i i种物品和前 i − 1 i-1 i−1种物品考虑选择方案得到的结果之间的关系。因此,也选择消去 i i i这个维度。
- 由于 f [ j ] = m a x ( f ( j , j − v [ i ] ) + w [ i ] ) f[j] = max(f(j, j - v[i]) + w[i]) f[j]=max(f(j,j−v[i])+w[i]),可以知道需先求出 f ( i , j − v [ i ] ) f(i,j-v[i]) f(i,j−v[i]),而后根据 f ( i , j − v [ i ] ) f(i,j-v[i]) f(i,j−v[i])求算 f ( i , j ) f(i,j) f(i,j)。因此, j j j是依次增大的循环。并且由于 j > = v [ i ] j >= v[i] j>=v[i],所以 j j j从 v [ i ] v[i] v[i]开始循环,到最大值结束。