有N个物品,需要装进大小为M的背包中,每件物品的大小和价值为w和m,求能得到的最大价值。
输入
第一行为N和M
之后N行为第i个物品的W和M
输出
最大价值
案例:
5 10
5 3
3 6
7 8
5 9
2 4
dp[i][j]表示: 考虑前 i 个物品,总大小为 j 的最大收益
递推公式:
对于第 i 个物品,大小为 j 时的最大收益不满足v[ i ]则不取,即 dp[ i-1 ][ j ]
取 dp[ i-1 ][ j-v[i] ] + w[ i ] //前 i-1 个物品大小为j-v[ i ]的最大收益+当前物品收益
不取 dp[ i-1 ][ j ] //前 i-1 个物品大小为 j 的最大收益初始化:
每个dp[ i ][ j ] 和 前 i-1 有关
当 i = 1 时,对于 i = 0,全为 0 不影响结果
所以全部初始化为 0 即可
遍历顺序
每个dp[ i ][ j ] 和 前 i-1 有关
所以需要外层 i 从 1 到 n
内层 j 从 (1 到 m) 或 (m 到 1)都可以
打印
即为dp[ i ][ j ]
对于打印为什么不是从dp[ i ]中选取最大值?
因为刚开始全部初始化为 0 ,而空间最大为最大的值
int n,m,dp[1001][1001],v[1001],w[1001];
int main(){
//输入
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++)
scanf("%d%d",&v[i],&w[i]);
//dp
for(int i=1; i<=n; i++)
for(int j=0; j<=m; j++)
if(j<v[i]) //如果 j 比 v[i] 的空间还小
dp[i][j] = dp[i-1][j];
else
dp[i][j] = max(dp[i-1][j-v[i]] + w[i], dp[i-1][j]);
//输出
for(int i=1; i<=n; i++){
for(int j=0; j<=m; j++)
printf("%d\t",dp[i][j]);
printf("\n");
}
return 0;
}
输出结果:
0 0 0 0 0 3 3 3 3 3 3
0 0 0 6 6 6 6 6 9 9 9
0 0 0 6 6 6 6 8 9 9 14
0 0 0 6 6 9 9 9 15 15 15
0 0 4 6 6 10 10 13 15 15 19
我们发现该代码所需空间太大复杂度为O(NM)。我们可以继续观察到每次我们更新的第 i 列dp数组只与第 i-1 列的数组有关,所以可以考虑滚动数组,压缩空间。
这里我们使用两个数组,d1 和 d2 分别代表每次的 上一列 和 这一列 。
int n,m,dp[1001][1001],v[1001],w[1001];
int d1[1001],d2[1001];
int main(){
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++)
scanf("%d%d",&v[i],&w[i]);
//dp
for(int i=1; i<=n; i++){
for(int j=0; j<=m; j++)
if(j<v[i])
d2[j] = d1[j];
else
d2[j] = max(d1[j],d1[j-v[i]]+w[i]);
memcpy(d1,d2,sizeof(d2)); //将d2数组的值赋予d1
}
return 0;
}
d2 数组的每一行更新后为:
0 0 0 0 0 3 3 3 3 3 3
0 0 0 6 6 6 6 6 9 9 9
0 0 0 6 6 6 6 8 9 9 14
0 0 0 6 6 9 9 9 15 15 15
0 0 4 6 6 10 10 13 15 15 19
空间复杂度为O(m)!我们能不能用O(N)的复杂度呢?
我们每一次更新第 i 行 j 个时,只与第 i - 1 行的 第 j 个或者第 j-v[i]个有关
因此我们可以每次从后往前依次更新一个数组
代码如下:
int n,m,dp[1001],v[1001],w[1001];
int main(){
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++)
scanf("%d%d",&v[i],&w[i]);
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]);
//输出每一行
for(int j=0;j<=m;j++)
printf("%d ",dp[j]);
printf("\n");
}
return 0;
}
关于 j 的边界,之前从前往后遍历是 j < v[ i ]的时候不更新,所以这个时候要到j >= v[ i ]即可结束
如果第 i 行的 v[ i ] 比第 i-1 个大,那么前面会有一部分没有更新,但是这一段的 dp 的值在之前的代码 j < v[ i ] 时: dp[i][j] = dp[i-1][j] 即当前代码还是上一行的,也就是可以不用更新。所以所得的值最终与第一版相同
0 0 0 0 0 3 3 3 3 3 3
0 0 0 6 6 6 6 6 9 9 9
0 0 0 6 6 6 6 8 9 9 14
0 0 0 6 6 9 9 9 15 15 15
0 0 4 6 6 10 10 13 15 15 19