01背包
二维数组代码
for(int i = 1;i <= n;i ++)
for(int j = 1;j <= m;j ++){
dp[i][j] = dp[i-1][j];
if(j >= v[i])
dp[i][j] = max(dp[i][j], dp[i-1][j-v[i]] + w[i]);
}
cout << dp[n][m] << endl;
一维代码
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;
因为要确保每一个物品只是用一次,从大到小遍历的话就保证了物品不会重复选取
TLE代码,暴力 O(N * M * K)
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++){
dp[i][j] = dp[i-1][j];
for(int k = 1; 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;
二维变一维代码
为什么可以从该点直接遍历呢
f [ i ][ j ] = max(f [ i - 1 ][ j ], f [ i - 1][j - v] + w, f [ i - 1][j - 2v] + 2w, … f [ i - 1][j - sv] + sw,
f [ i ][ j - v ] = max( f [ i - 1 ][ j ] , f [ i - 1][j - 2v] + w, … f [ i - 1][j - sv] + (s - 1)w,
中间都是相同的,只是全多了一个w,还有即使第一项
so : f [ i ][ j ] = max (f[ i - 1][ j ], f[ i ][ j - v ] + w)
for(int i = 1; i <= n; i ++)
for(int j = v[i]; j <= m; j ++)
dp[j] = max(dp[j], dp[j - v[i] ] + w[i]);
cout << dp[m] << endl;
TLE代码,暴力 O(N * M * K)
for(int i = 1;i <= n;i ++)
for(int j = 0;j <= m;j ++){
dp[i][j] = dp[i-1][j];
for(int k = 1;k <= s[i] && 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;
这样的复杂度实在是太高了,那么能不能用上面的方法进行优化呢
f [ i ][ j ] = max(f [ i - 1 ][ j ], f [ i - 1][j - v] + w, f [ i - 1][j - 2v] + 2w, … f [ i - 1][j - sv] + sw,
f [ i ][ j - v ] = max( f [ i - 1 ][ j ] , f [ i - 1][j - 2v] + w, … f [ i - 1][j - sv] + (s - 1)w, f [ i - 1][j - (s + 1)v] + s w,
因为每个物品限制了个数,而这个个数并不是体积的临界值,所以f [ i ][ j ] 和 f [ i ][ j - v ] 都有可能是选择s个物品,这样的话就看最后一个值能不能继续转化
多了最后的一项,
知道一个序列的最大值后,去掉最后一项后前面的最大值可知吗
答案是不可知的,
无法利用上面的方法进行优化
那么怎么办呢
比如对于一个物品来说,最多选s个,那么有可能选的即为0~s这里面的
原来的方法是for一遍,可是这样的效率太低了,
那么就是怎么表示这0~s这些数字,不难想到是二进制啊,
之前的 lca算法 也是,由于挨个遍历效率太低了,所以就用二进制进行跳跃遍历
把这个数拆解成二进制,然后用这些二进制数可以表示所有的小于等于该数的数
转换为二进制后,发现每个新的背包要么选择,要么不选择,那么就是01背包了
把多重背包变换为二进制的01背包,在这些二进制的01背包内进行选择,
这样就是利用消耗空间去换取时间
比如 s = 13,那么怎么进行二进制匹配呢,
1 2 4 5,为什么这样是对的呢,因为1 2 4 可以表示 0 ~ 9 内所有的数,而 0 ~ 9 全部加上 5 即可表示 5 ~ 13 内所有的数, 这样 1 2 4 5 就可以表示0 ~ 15 内所有的数字了
N(新背包的种类) = n(原背包的种类) * log s(对于每个原背包最大的个数进行二进制匹配)
int main(){
cin >> n >> m;
while(n --){
int a, b, c;
cin >> a >> b >> c;
int t = 1;
while(c >= t){//二进制匹配
c -= t;
cnt ++;
w[cnt] = t * b;
v[cnt] = t * a;
t *= 2;
}
if(c){//对于剩下的数存在一个背包内
cnt ++;
w[cnt] = c * b;
v[cnt] = c * a;
}
}
for(int i = 1; i <= cnt; i ++)
for(int j = m; j >= v[i]; j --){
dp[j] = max(dp[j], dp[j-v[i]] + w[i]);
}
cout << dp[m];
return 0;
}
每组内只选择一个物品,每一次选择物品的时候都是由上一层转化来,这样就保证了每一层只选一个
二维代码
for(int i = 1; i <= n; i ++)
for(int j = 0; j <= m; j ++){
dp[i][j] = dp[i-1][j];
for(int k = 1; k <= s[i]; k ++)
if(j >= v[i][k])
dp[i][j] = max(dp[i][j], dp[i-1][j - v[i][k]] + w[i][k]);
}
一维代码
for(int i = 1; i <= n; i ++)
for(int j = m; j >= 0; j --)
for(int k = 1; k <= s[i]; k ++)
if(j >= v[i][k]) //体积得符合要求
dp[j] = max(dp[j], dp[j - v[i][k]] + w[i][k]);