目录
题目
/*
有 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
*/
01背包问题回顾
https://editor.csdn.net/md/?articleId=105723988
与01背包问题类似,dp[i][j]还是表示在背包是j容积下只拿前i个物品的价值最大的情况,不同就是物品可以重复拿i个物品.
完全背包 朴素算法
#include<iostream>
using namespace std;
const int N = 1010;
int n, m;
int dp[N][N], v[N], w[N];
int main(){
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 ++ )
dp[i][j] = max(dp[i][j], dp[i - 1][j - k * v[i]] + k * w[i]);
cout << dp[n][m] << endl;
}
为什么可以消去K的for循坏,优化代码.
集族:就是集合构成的集合,通俗理解为集合的族群
完全背包问题复杂性就是有很多不同的集合构成了一个集族群,dp[i][j]就是要从整个族群中找出价值最大的情况
dp(i,j)表示集族{ {拿了0个i物品的最大值},{拿了1个i物品的最大值},{拿了2个i物品的最大值},…,{拿了k个i物品的最大值} }中的最大值.
拿了0个i物品(没有拿i个物品)的集合是一定有的。因为内存原因,k个i物品的甚至是1个i物品都可能都没有对应的的集合
表示一下这个集族 dp[i][j]和集族dp[i][j-v],对比两个集族的区别:注意看dp[i][j]中多出来的W
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v] + w, dp[i - 1][j - 2 * v] + 2 * w, dp[i - 1][j - 3 * v] + 3 * w);
dp[i][j - v] = max( dp[i - 1][j - v] , dp[i - 1][j - 2 * v] + w, dp[i - 1][j - 3 * v] + 2 * w, .....);
当j = j-v时候,带入状态方程,发现从第2项起来,f(i,j)集族中的集合最大值都比f(i,j-v)集族对应的集合的最大值多了w,所以可以用集族 f(i,j-v)+w 表示f(i,j)集族中从第二个元素起的后面所有的最大值情况.
有了这个集族的概念,知道每项最大值的关系,f(i,j)是在集族中的最大值.,要么是不拿i个物品保持i-1时的最大值,或者是拿i个物品在d[i,j-v]+w中选,这样就可以取消K的for循环了。
注:因为i是外层循环,j是正序遍历越来越大,所以i在j(容量)变大的时候是不变的,只要是当前最优就是可以重复拿。
此时集族dp[i][j]最大值的的表示是:
将每一项一一比对,我们可以得到下列状态表示:
dp[i][j] = max(dp[i - 1][j], dp[i][j - v] +w);
优化后使用2维数组+2重循坏的代码
#include<iostream>
using namespace std;
const int N = 1010;
int n, m;
int dp[N][N], v[N], w[N];
int main(){
cin >> n >> m;
for(int i = 1; i <= n; i ++ ){
int v, w;
cin >> v >> w;
for(int j = 0; j <= m; j ++ ){
dp[i][j] = dp[i - 1][j];
if(j >= v)
dp[i][j] = max(dp[i][j], dp[i][j - v] + w);
}
}
cout << dp[n][m] << endl;
}
利用滚动数组取消二维数组
#include<stdio.h>
#define maxn 1010
#define max(a,b) a>b?a:b
int v[maxn],w[maxn];//v[i],w[i]分别表示第i件物品的体积和价值。
int dp[maxn];//dp[i][j]表示前i件物品,体积刚好为j时最大价值。
int main()
{
int n,V;//n为物品数量,v为背包容积
scanf("%d%d",&n,&V);
for(int i=1;i<=n;i++)
{
scanf("%d%d",&v[i],&w[i]);
}
for(int i=1;i<=n;i++) //i表示前i件
{
for(int j=v[i];j<=V;j++) //j表示体积刚好为j
{
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
}
}
printf("%d\n",dp[V]); //输出最大价值
return 0;
}
过程分析
对比0,1背包问题代码,区别在于第二个for循环是正序不是逆序,因为每个物品可以无限拿,不用考虑上一个状态的值是否被覆盖。
01背包的状态转移方程:f[i][j] = max(f[i-1][j], f[i-1][c-w[i]] + v[i])
都依赖于i-1状态的值。
完全背包状态转移方程: f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
可以多次减去v[i],见下面演示过程。
01背包问题博客:
https://blog.csdn.net/qq_41398619/article/details/105723988
,可以参照博客中石墨文档中的笔记。
首先dp数组初始化全为0:给定物品种类有4种,包最大体积为5,数据来源于题目的输入
v[1] = 1, w[1] = 2
v[2] = 2, w[2] = 4
v[3] = 3, w[3] = 4
v[4] = 4, w[4] = 5
i = 1 时: j从v[1]到5
dp[1] = max(dp[1],dp[0]+w[1]) = w[1] = 2 (用了一件物品1)
dp[2] = max(dp[2],dp[1]+w[1]) = w[1] + w[1] = 4(用了两件物品1)
dp[3] = max(dp[3],dp[2]+w[1]) = w[1] + w[1] + w[1] = 6(用了三件物品1)
dp[4] = max(dp[4],dp[3]+w[1]) = w[1] + w[1] + w[1] + w[1] = 8(用了四件物品1)
dp[5] = max(dp[3],dp[2]+w[1]) = w[1] + w[1] + w[1] + w[1] + w[1] = 10(用了五件物品)
i = 2 时:j从v[2]到5
dp[2] = max(dp[2],dp[0]+w[2]) = w[1] + w[1] = w[2] = 4(用了两件物品1或者一件物品2)
dp[3] = max(dp[3],dp[1]+w[2]) = 3 * w[1] = w[1] + w[2] = 6(用了三件物品1,或者一件物品1和一件物品2)
dp[4] = max(dp[4],dp[2]+w[2]) = 4 * w[1] = dp[2] + w[2] = 8(用了四件物品1或者,两件物品1和一件物品2或两件物品2)
dp[5] = max(dp[5],dp[3]+w[2]) = 5 * w[1] = dp[3] + w[2] = 10(用了五件物品1或者,三件物品1和一件物品2或一件物品1和两件物品2)
i = 3时:j从v[3]到5
dp[3] = max(dp[3],dp[0]+w[3]) = dp[3] = 6 # 保持第二轮的状态
dp[4] = max(dp[4],dp[1]+w[3]) = dp[4] = 8 # 保持第二轮的状态
dp[5] = max(dp[5],dp[2]+w[3]) = dp[4] = 10 # 保持第二轮的状态
i = 4时:j从v[4]到5
dp[4] = max(dp[4],dp[0]+w[4]) = dp[4] = 10 # 保持第三轮的状态
dp[5] = max(dp[5],dp[1]+w[4]) = dp[5] = 10 # 保持第三轮的状态
上面模拟了完全背包的全部过程,也可以看出,最后一轮的dp[m]即为最终的返回结果。
作者:polaris
链接:https://www.acwing.com/solution/acwing/content/3986/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
根据模拟过程可以发现,遍历每一轮i时,用到的v[i]和w[i]只来自本轮的输入,并且之后不会再用到
还没明白的小伙伴的可以看看这个大佬的题解和视频讲解,有图片说明思路生动形象,代码一步步优化
题解
https://www.acwing.com/solution/acwing/content/5345/
大佬视频讲解
https://www.acwing.com/video/945/