这道题对我来说难的要命啊,以下是我对本题目的一个思考过程:
首先可知有三个状态,一个是01背包,一个是完全背包,一个是多重背包。
那行,我们就分成三个状态来解决问题吧!
首先我们定义f(i,j)是前i件物品,体积不超过j的最大值。
由题意可知,体积v,价值w,状态s
可以发现,根据基本的问题,得到以下状态计算:
(s==-1):f[i,j]=max(f[i-1,j],f[i-1,j-v]+w);
(s==0):f[i,j]=max(f[i-1,j],f[i-1,j-v]+w,…,f[i-1,j%v]+(j-j%v)w/v);
(s>0):f[i,j]=max(f[i-1,j],f[i-1,j-v]+w,…,f[i-1,j-sv]+s*w);
ps:这样三个状态就定义出来了,至于完全背包中可以用f[i,j-v]来化简这件事,我认为因为上一个状态不一定是完全背包问题,所以这个化简我觉得不能用。但是非常神奇的是就算用了也不会报错,有哪位大佬解答一下嘛?
好的好的,这样分析完之后我们开始化简一下吧!
可以发现,假设k是第i次可以取多少个第i个物品:
01背包就变成k的取值从0到1
完全背包变成k取值在j-kv>0的条件下从0取到最大值
多重背包变成k取值从0取到s(保证一下j-k*v>0,小于0没意义了)
也就是说,这些问题只是k的取值不一样而已,这样我们限定一下k的取值就好啦!
那么我们编写代码吧:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m;
int w[N], f[N][N];
int main() {
int res = 0;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
int v, w, s;
cin >> v >> w >> s;
if(s==-1)s=1;
if(s==0)s=m/v;
for (int j = 0; j <= m; j++) {
for (int k = 0; k <= s; k++) {
if(j - k*v>=0){
f[i][j]=max(f[i][j], f[i-1][j - k * v] + k * w);
res = max(res, f[i][j]);
}else{
break;
}
}
}
}
cout << res << endl;
return 0;
}
代码还是挺清晰明了的,而且好写!唯一不足的就是会tle。那咋办捏?
所以我们只能采用二进制状态优化了(这个是跟别人偷师的,我想破头都想不出来这个解法)
就是把可以采用的状态都用二进制来表示,第i次可以取的k用1,2,4,8…来表示,但是不可能都正好是2的倍数,那么我们是不是就要考虑一下怎么才能使用最正好的捏?
我们可以先减到不能减去位置,剩下的状态(余数)单独处理!
比如说表示10,那么我们就可以用1,2,4表示0-7,剩下3单独处理。
上面的集合加上3的话新的集合可以表示的范围就从0-7变成0-10!(咋来的捏,就是5+3=8,6+3=9,7+3=10嘛,反正0-7都可以表示了)
wow,我们把所有问题都归化到01背包问题了,状态就是第i个物品,每个取不取得部分就是分成的二进制堆数!
好家伙,01背包问题的话连二维状态都不用了,一维就可以了,那么这样的话我们就会得到以下代码,终于完成了!
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 2010;
int f[N];
int main() {
int n, m;
cin >> n >> m;
for (int i = 0; i < n; i++) {
int v, w, s;
cin >> v >> w >> s;
for (int k = 1; k <= s; k *= 2) {
for (int j = m; j >= k * v; j--) {
f[j] = max(f[j], f[j - k * v] + k * w);
}
s -= k;
}
if (s > 0) { // 处理余数
for (int j = m; j >= s * v; j--) {
f[j] = max(f[j], f[j - s * v] + s * w);
}
}
}
cout << f[m] << endl;
return 0;
}
AC真不容易啊!