3. 完全背包问题
题目描述
有 N种物品和一个容量是 V的背包,每种物品都有无限件可用。
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10
动态规划
一维数组
这段代码演示了如何解决一个经典的动态规划问题,即完全背包问题。注释已经添加在代码的相应部分,以便详细解释每一步。
#include<bits/stdc++.h> // 引入所有标准库
using namespace std;
int main()
{
int n,v; // n 表示物品种数,v 表示背包容量
cin>>n>>v; // 输入物品种数和背包容量
vector<int> val(n+1,0),w(n+1,0); // val 存放物品价值,w 存放物品体积
vector<int> dp(v+1,0); // dp 数组,用于存放每个容量下的最大价值
for(int i=0;i<n;i++)
cin>>w[i]>>val[i]; // 输入每种物品的体积和价值
// 动态规划过程
for(int i=0;i<n;i++) // 遍历所有物品
{
// 完全背包的特点是,每种物品可以选无限次,所以内循环正序遍历
for(int j=w[i];j<=v;j++) // 对于每个容量,从当前物品的体积开始遍历到背包容量
{
// 状态转移方程,尝试将当前物品加入背包,并更新最大价值
dp[j]=max(dp[j],dp[j-w[i]]+val[i]);
}
}
// 输出最大价值,即背包容量为v时的最大价值
cout<<dp[v];
return 0;
}
在这个代码中,dp[j]
表示的是背包容量为j
的情况下能够装入物品的最大价值。在内循环中,我们尝试将每件物品加入背包中,并更新dp[j]
为当前dp[j]
与dp[j-w[i]]+val[i]
中的较大值,其中dp[j-w[i]]+val[i]
代表在背包中已经装有一定体积物品的情况下再加入当前考虑的物品所能达到的价值。
完全背包问题与0-1背包问题的重要区别在于:完全背包问题中的每种物品可以选取无限次,而0-1背包问题中每种物品只能选取一次。
二维数组
三重循环(超时)
这段代码是用来解决完全背包问题的,完全背包问题允许每种物品可以被无限次选取。代码使用的是动态规划的方法,其核心思想是利用之前的计算结果来优化后续的计算。
#include<bits/stdc++.h> // 引入所有标准库
using namespace std;
// 给定两个数组,val 和 w,分别存储每种物品的价值和体积
int val[1010], w[1010];
// dp[i][j] 表示考虑前i件物品,当背包容量为j时可以得到的最大价值
int dp[1010][1010];
int main()
{
int n, v; // n 表示物品种类数量,v 表示背包的容量
cin >> n >> v; // 从标准输入读取 n 和 v
for(int i=1; i<=n; i++)
cin >> w[i] >> val[i]; // 读取每种物品的体积和价值
// 主循环开始,对每一种物品进行考虑
for(int i=1; i<=n; i++)
{
// 内层循环,遍历所有的容量选项从0到v
for(int j=0; j<=v; j++)
{
// 内层循环,考虑取0到最大可能数量k的第i种物品
for(int k=0; k*w[i]<=j; k++)
{
// 状态转移方程,尝试在保持之前物品不变的情况下
// 加入k个第i种物品,如果获得更大价值,则更新dp[i][j]
dp[i][j] = max(dp[i][j], dp[i-1][j-k*w[i]]+k*val[i]);
}
}
}
// 输出最大价值,即考虑所有物品种类且背包容量为v时的最大价值
cout << dp[n][v];
return 0;
}
这段代码实现了一个三重循环。外层循环遍历所有物品,中层循环遍历所有可能的背包容量,内层循环则遍历对于当前考虑的物品i,可以选择的数量。状态转移方程的含义是:考虑前i件物品,当背包容量为j时,可以选择不放入当前物品(保持 dp[i][j]
的值),或者放入k个第i种物品(在保证总体积不超过j的情况下,尽可能使总价值最大化)。
最后,dp[n][v]
存储的即为最大价值,输出即可。这个代码的时间复杂度是O(nv数量),其中n是物品种数,v是背包容量,数量是指每种物品能放入背包的最大件数,这里未优化的三重循环会比较慢。实际应用中通常会使用优化方法,如将三重循环优化为二重循环,从而减少计算时间。
双重循环
这段代码是一个更优化的版本,用来解决同样的完全背包问题。不同于之前的三重循环,这里通过一个双重循环以更高的效率解决问题。它依然使用动态规划的思想,但是利用了完全背包问题的特性来简化计算。下面是对代码的逐行注释:
#include<bits/stdc++.h> // 包含所有标准库
using namespace std;
// val数组存储每种物品的价值,w数组存储每种物品的体积
int val[1010], w[1010];
// dp数组,dp[i][j]表示考虑前i件物品,当背包容量为j时可以得到的最大价值
int dp[1010][1010];
int main()
{
int n, v; // n表示物品种类数量,v表示背包的容量
cin >> n >> v; // 从标准输入读取n和v
for(int i=1; i<=n; i++)
cin >> w[i] >> val[i]; // 读取每种物品的体积和价值
// 开始双重循环,遍历所有物品和背包容量
for(int i=1; i<=n; i++)
{
for(int j=0; j<=v; j++)
{
// 先继承上一行的值,即不考虑新增的这个物品时的最大价值
dp[i][j] = dp[i-1][j];
// 如果当前背包容量可以容纳至少一个当前遍历到的物品
if(j >= w[i])
// 更新dp[i][j]为不取或者取当前物品的最大价值
// dp[i][j-w[i]]+val[i] 表示取当前物品时的价值,因为是完全背包,所以仍然是i,不是i-1
dp[i][j] = max(dp[i][j], dp[i][j-w[i]] + val[i]);
}
}
// 输出最大价值,即考虑所有物品种类且背包容量为v时的最大价值
cout << dp[n][v];
return 0;
}
这段代码通过动态规划解决完全背包问题,核心优化在于内层循环的改进。在考虑每种物品时,它不需要再枚举这种物品的具体数量,而是直接基于前一个状态进行更新。这样的优化减少了计算量,因为每次都是在原来的基础上尝试增加当前物品,直接利用了之前计算的结果。
动态规划数组dp[i][j]
的更新逻辑是:对于每个物品i
和每种容量j
,它可以选择不加入当前物品,此时价值为dp[i-1][j]
;或者选择加入当前物品(前提是j
的容量大于等于物品i
的体积w[i]
),此时的价值变为dp[i][j-w[i]] + val[i]
,表示在当前容量下加入物品i
后的价值(因为物品有无限个,所以还是在dp[i][...]
上进行操作,而非dp[i-1][...]
)。最终,dp[n][v]
存储的就是考虑所有物品和整个背包容量时的最大价值。