公式
- 背包问题的思考方向不是有多大的包要选哪些物品,而是腾出足够的大小放物品
dp[i][j] = max{dp[i-1][j-k*vi]+k*wi} 0<=k<=ni && k*vi<=j
vi,wi,ni分别是物品i的体积,价值和个数,j是当前背包的容积
dp[i][0] = dp[i-1][0]
dp[i][1] = dp[i-1][1]
...
dp[i][vi-1] = dp[i-1][vi-1]
dp[i][vi] = max{ dp[i-1][vi], dp[i-1][0]+wi}
dp[i][vi+1] = max{ dp[i-1][vi+1], dp[i-1][1]+wi}
...
dp[i][2*vi-1] = max{dp[i-1][2*vi-1], dp[i-1][vi-1]+wi}
dp[i][2*vi] = max{ dp[i-1][2*vi], dp[i-1][vi]+wi, dp[i-1][0]+2*wi}
dp[i][2*vi+1] = max{dp[i-1][2*vi+1], dp[i-1][vi+1]+wi, dp[i-1][1]+2*wi}
...
dp[i][3*vi-1] = max{dp[i-1][3*vi-1], dp[i-1][2*vi-1]+wi, dp[i-1][vi-1]+2*wi}
dp[i][3*vi] = max{ dp[i-1][3*vi], dp[i-1][2*vi]+wi, dp[i-1][vi]+2*wi, dp[i-1][0]+3*wi}
...
- 可以看出后vi个会在前vi个的基础上多加一项求最值,以vi为步长,需要的dp数组的元素高度重复
- 比如
dp[i][0] = dp[i-1][0]
dp[i][vi] = max{ dp[i-1][vi], dp[i-1][0]+wi}
dp[i][2*vi] = max{ dp[i-1][2*vi], dp[i-1][vi]+wi, dp[i-1][0]+2*wi}
dp[i][3*vi] = max{ dp[i-1][3*vi], dp[i-1][2*vi]+wi, dp[i-1][vi]+2*wi, dp[i-1][0]+3*wi}
- 上述只涉及dp的四个元素:dp[i-1][0],dp[i-1][vi],dp[i-1][2*vi],dp[i-1][3*vi]。同时因为求的是相对大小,可以同时减去k*wi转化一下,即变成
dp[i][0] = dp[i-1][0]
dp[i][vi] = max{ dp[i-1][vi]-wi, dp[i-1][0]}
dp[i][2*vi] = max{ dp[i-1][2*vi]-2*wi, dp[i-1][vi]-wi, dp[i-1][0]}
dp[i][3*vi] = max{ dp[i-1][3*vi]-3*wi, dp[i-1][2*vi]-2*wi, dp[i-1][vi]-wi, dp[i-1][0]}
- 显然,每vi个求最大值可以使用单调队列优化
- 因为dp[i]只需要dp[i-1]的数据,可以空间优化,使用一维数组
- 伪代码
for 物品i:
for j in 0...vi-1:
for k in 0...(总容积-j)/vi:
dp[k*vi+j]-k*wi进队
队首的k值小于当前循环的k-ni,队首出队
dp[k*vi+j]=队首+k*wi
代码
#include <deque>
#include <vector>
#include <iostream>
using namespace std;
struct lwd {
int val;
int k;
lwd(int v, int kk) {val=v, k=kk;}
};
int multipack(vector<int> v, vector<int> w, vector<int> n, int pack) {
vector<int> dp(pack+1);
for(int i = 0; i < v.size(); ++i) {
for(int j = 0; j < v[i]; ++j) {
deque<lwd> dq;
int end = (pack-j)/v[i];
for(int k = 0; k <= end; ++k) {
int val = dp[j+k*v[i]] - k*w[i];
while(!dq.empty() && dq.back().val <= val) dq.pop_back();
dq.push_back(lwd(val, k));
if(dq.front().k < k-n[i]) dq.pop_front();
dp[j+k*v[i]] = dq.front().val + k*w[i];
}
}
}
return dp[pack];
}
int main() {
int N=4;
vector<int> v(N), w(N), n(N);
for(int i = 0; i < N; ++i) {
v[i] = i+1;
}
w[0]=2, w[1]=w[2]=4, w[3]=5;
n[0]=3, n[1]=1, n[2]=3, n[3]=2;
cout << multipack(v,w,n,5) << endl;
return 0;
}
参考