方法:直接寻找递推关系
第
i
−
1
i-1
i−1 件物品选
k
k
k 个
int dp[maxn+1][maxn+1];
void solve(){
for(int i=0;i<n;i++)
for(int j=0;j<=W;j++)
for(int k=0;k*w[i]<=j;k++)
dp[i+1][j]=max(dp[i+1][j],dp[i][j-k*w[i]]+k*v[i]);
cout<<dp[n][W];
}
k的循环最坏可能从0到W,所以算法时间复杂度为 O ( n W 2 ) O(nW^2) O(nW2)
我们寻找这个算法中多余的计算(已知结果的计算)
在dp[i+1][j]
的计算中选择
k
(
k
≥
1
)
k(k≥1)
k(k≥1) 个的情况,与在dp[i+1][j-w[i]]
的计算中选择
k
−
1
k-1
k−1 个的情况是相同的,所以dp[i+1][j]
的递推中
k
≥
1
k≥1
k≥1 部分的计算已在dp[i+1][j-w[i]]
的计算中完成了。则可按照如下方式变形:
void solve(){
for(int i=0;i<n;i++)
for(int j=0;j<=W;j++){
if(i<w[i]) dp[i+1][j]=dp[i][j];
else dp[i+1][j]=max(dp[i][j],dp[i+1][j-w[i]]+v[i]);
}
cout<<dp[n][W];
}
时间复杂度为 O ( n W ) O(nW) O(nW)。完全背包问题也可以通过不断重复利用一个数组来实现:
int dp[maxn+1];
void solve(){
for(int i=0;i<n;i++)
for(int j=0;j<=W;j++)
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
cout<<dp[W];
}
DP数组的再利用
还可能通过将两个数组滚动使用来实现重复利用。例如
dp[i+1][j]=max(do[i][j],dp[i+1][j-w[i]]+v[i])
这一递推式中,dp[i+1]
计算时只需要dp[i]
和dp[i+1]
,所以可以结合奇偶性写成如下形式:
int dp[2][maxn+1];
void solve(){
for(int i=0;i<n;i++)
for(int j=W;j<=W;j++){
if(j<w[i]) dp[(i+1)&1][j]=dp[i&1][j];
else dp[(i+1)&1][j]=max(dp[i&1][j],dp[(i+1)&1][j-w[i]]+v[i]);
}
cout<<dp[n&1][W];
}