物品有数量限制

先自己想想

那根据完全背包的意思,三层循环的情况下,就是把边界条件改成 k*v[i]<=j && k<=s[i]

关键就是三重循环肯定不对,要优化

状态计算

先考虑 完全背包 的方式可以吗?

f[i][j] = max(f[i-1][j], f[i-1][j-v]+w, ... , f[i-1][j-sv]+sw)
f[i][j-v] = max(         f[i-1][j-v]  , ... , f[i-1][j-sv]+(s-1)w, f[i-1][j-(s+1)v]+sw)

由于 完全背包 是无限个,终止条件是背包装不下,所以后半截数量是对上的。但是多重背包有可能提前终止,那就会多这么一项,所以不能

二进制优化法

感觉就是用奇技淫巧把多重背包转变为 01 背包问题,类似那道 删除并获得点数 转变为 打家劫舍,将原问题的集合构造成一个新的集合

所以为什么用二进制,因为二进制是 01 的,每一位选或不选可以构造出 s[i]

因此就能把三层循环中的其中一层优化为 logN

具体如何表示

如果 s=200,那是按照惯例 200 255 吗?不是的,而是 200 = 127+73

这么做的原因是不想变复杂,又不是真的编码,只是想要一种方式能够刚好覆盖 0~200

#include <iostream>
#include <algorithm>
 
const int N = 25000;  // 这里已经 N * logS 了
int v[N], w[N];
int dp[N];
 
int main() {
	cin >> n >> m;
 
	int cnt = 1;  // 我这里还是显式地强调一下从 1 开始
	for(int i = 0; i < n; ++i) {  // 这里要重新构造问题集合
		int a, b, s;  // 体积,价值,数量
		cin >> a >> b >> s;
 
		int k = 1;
		while(k <= s) {
			v[cnt] = k * a;
			w[cnt] = k * b;
			cnt ++ ;
 
			s -= k;
			k *= 2;
		}
		if(s > 0) {  // 不是刚好的数
			v[cnt] = s * a;
			w[cnt] = s * b;
			cnt ++ ;
		}
	}
 
	n = cnt;  // 将问题规模重置一下
	// 然后开始 01 背包
	for(int i = 1; i <= n; ++i) {
		for(int j = m; j >= v[i]; --j) {
			dp[j] = max(dp[j], dp[j-v[i]] + w[i]);
		}
	}
 
	cout << dp[m] << endl;
}