学习自acwing
01背包问题
问题描述
有N件物品,每件物品只能使用一次。
每件物品有体积Vi,价值Wi。
有容量是V的背包,求物品总体积不超过背包容积的最大价值。
解题思路
DP
状态表示:f[i][j]
表示从前 i 个物品中选,总体积不超过 j 的方案中,价值最大的。
也就是前i个物品中选,总体积不超过j的方案的最大价值。
**状态计算:**从最后一位开始思考和划分。这里划分2种情况,划分的依据是不重不漏。

- 不选择第i个物品,那么前
i-1
个物品的价值仍然不变。
f[i-1][j]
- 选择第i个物品,那么第i个物品为必选,前i-1个物品的情况是已是最优情况。
f[i-1][j-v[i]]+w[i]
示意图如下:
f[i][j]
就是上面两种情况求个max,就是取这两种情况下价值大的价值作为该状态的值。
代码:f[i][j] = f[i-1][j] + f[i-1][j-v[i]]+w[i]
注意:如果背包容量不够放下第i个物品,那么当前状态不变
f[i][j] = f[i-1][j]
**初始化:**前0个物品中,总体积不超过0~背包体积m的最大价值为0。
f[0][0~m] = 0
实际上无需做操作,因为全局变量的默认值就是0。
完整思路图
完整代码:
朴素写法:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1005;
int v[N],w[N]; //物品的体积和价值
int f[N][N]; //状态转移方程,f[i][j]表示从前i个物品中选,总体积不超过j的最大价值
int main() {
int n,m; //背包数量和容积
cin >> n >> m;
for(int i = 1; i <= n; i++) //注意从1~n
scanf("%d%d",&v[i],&w[i]);
//f[0][0~m]=0为初始状态,默认为0
for(int i = 1; i <= n; i++) { //注意要包括n
for(int j = 0; j <= m; j++) { //注意要包括m
if(j < v[i]) //装不下第i个物品
f[i][j] = f[i-1][j];
else
f[i][j] = max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
}
}
cout << f[n][m] << endl; //n个物品中选,总体积不超过m的最大价值
}
利用滚动数组思想,转换成一维的做法。这样时间复杂度更低。
转换的方法就是,直接删掉一维,然后思考一下是不是等价的,再进行一些修改。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m; //物品数量、背包容积
int f[N]; //总体积<=j的最大价值
int main() {
cin >> n >> m;
for (int i = 0; i < n; i++) {
int v, w; //物品的体积、价值
cin >> v >> w;
for (int j = m; j >= v; j--) {
f[j] = max(f[j], f[j - v] + w);
}
}
cout << f[m] << endl;
return 0;
}
完全背包问题
问题描述
有N件物品,每件物品能无限使用。
每件物品有体积Vi,价值Wi。
有容量是V的背包,求物品总体积不超过背包容积的最大价值。
解题思路
状态表示:f[i][j]
表示从前i个物品中选,总体积不超过j的最大价值
状态计算:
01背包问题中,只需要考虑第i个物品选或者不选,而完全背包问题中,每个物品可以选无数多个,所以需要划分成n个子集。

每个子集表示第i个物品选0,1,2,…,k…个数量。然后再求一个max。
选0个:和01背包的情况一样
f[i-1][j]
选k个:

这里和01背包略有不同,需要容量需要减去k倍的v[i]
f[i-1][j-k*v[i]]
状态转移方程为

这样需要三重循环来达到效果,显然时间复杂度不达标,所以需要做一些优化。

上图可以看到,红色的部分其实完全等价,所以就可以做一个替换,把上式中所有的j都变成j-v。

这样就少了一重循环,实现这个dp只需要2重循环了。
状态转移方程为:f[i][j] = max(f[i-1][j],f[i][j-v[i]])+w[i]
完成思路图
再次对比一下01背包问题和完全背包问题的状态转移方程
/*
01背包问题:
f[i][j] = max(f[i-1][j],f[i-1][j-v[i]]+w[i])
完全背包问题:
f[i][j] = max(f[i-1][j],f[i][j-v[i]]+w[i])
*/
注意,只有一个区别,01背包问题是f[i-1][j-v]+w
,完全背包问题是f[i][j-v]+w
完整代码
朴素写法:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1005;
int v[N]; //每种物品的体积
int 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];
//初始化,f[0][0~m] = 0
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]) { //当前容量放得下v[i]
f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);
}
}
}
cout << f[n][m] << endl;
return 0;
}
利用滚动数组思想,优化后的代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1005;
int v[N]; //每种物品的体积
int 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;
}