物品只有拿或不拿

状态表示

集合:表示所有选法,是前 i 个物品中选,体积 j 的所有选法

属性:max;min;数量

相当于 dp[i][j] 表示前 i 个物品中选,体积 j 的所有选法的集合,就是说代表了这件事。然后这个值,为最大价值,具体的一个最大价值,这个集合中各种选法中的最大值

状态计算

就是集合划分,如何将现在的集合通过更小的集合表示出来

对于背包问题,经常这么划分:

  • 不包含 i 这个物品的,恰好就是 dp[i-1][j]
  • 包含 i 这个物品的,恰好就是 dp[i-1][j-vi] + vi

代码

#include <iostream>
#include <algorithm>
 
using namespace std;
 
const int N = 1010;
int n, m;
int v[N], w[N];
int dp[N][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++) {
			if(j >= v[i]) dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[i]] + w[i]);
			else dp[i][j] = dp[i-1][j];
		}
	}
	cout << dp[n][m] << endl;
}

这里我觉得判断 j 和 vi 的那一步还不是那么好想,如果直接说要背包容量足以装 vi 才行,但 j 不是总的背包容量吗?应该是剩余空间足够装 vi 才对

确实,所以建议先考虑因为要索引 j-v[i],所以考虑成索引合法就行了。然后再来细究实际意义

实际上,这是在倒推,我说我 i 物品一定要放,最极端的不就是 i 价值很高,刚好塞满背包吗

另外,值得考虑的是边界情况,i-1 也不能超出呀,所以 i 从 1 开始,边界的 00 就是都是默认 0 正好也可以

优化为一维

一般可以优化都是因为这一层只用了前一层或前两层。这里就是只用了前一层

注意到,else 那里,新一层和前一层持平,说明去掉第一维之后,不动就是更新了

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 = m; j >= v[i]; j--) {  // attention
			dp[j] = max(dp[j], dp[j-v[i]] + w[i]);
		}
	}
	cout << dp[m] << endl;
}

然后 j 是从小到大增加的,也就是 dp 是从前往后更新的,而每次更新要用到 j 之前的元素,可是那个已经被更新了,所以倒过来循环,更新的时候前面还没被更新