国王的金矿:动态规划中 “01背包问题” 的空间优化
上一篇:国王的金矿 经典解法,使用二维数组
https://blog.csdn.net/weixin_44176696/article/details/104063272
观察经典状态转移方程
根据刻在DNA里的状态转移方程:
// cost[x] / value[x] 表示开第x座矿的需要的人手,价值
dp[i][j] = max(dp[i-1][j], dp[i-1][j-cost[i]]+value[i]);
在更新第 i 行(即求取使用 0~全部人 开采前 i 座金矿的最大收益)的时候发现
- 第 i 行的更新,只用到了第 i-1 行保存的计算结果
- 第 i 行 第 j 列的更新,只用到 第 i-1 行的前 j 列保存的结果
空间优化
由于第 i 行 第 j 列的更新,只用到 第 i-1 行的前 j 列保存的结果
我们可以 “倒着看”,来更新dp数组
- dp数组是一维数组
- 在第 i 次的主循环的更新中(即求取使用 0~全部人 开采前 i 座金矿的最大收益),dp[j] 表示: 使用 j 人开采 [ 前 i-1 ] 座金矿的最大收益 ,也就是前一次主循环更新的结果
故可以得到状态转移方程为:
dp[j](新)= max(dp[j](旧), dp[j-cost[i]]+value[i]);
故有更新后dp[x]表示用 x 人开采前 i 座矿的最大收益
代码
注释详细 ↓
#include <iostream>
using namespace std;
#define maxlen 114
#define max_usable 1145
#define max(a, b) ((a>b)? (a):(b))
int n, usable; // 矿数,可用人数
int value[maxlen]; // 第i座矿的价值
int cost[maxlen]; // 开第i座矿需要的人数
// 第 i 次的主循环中的 dp[j]表示:
// 使用j人开采 [前i-1] 座金矿的最大收益
int dp[max_usable];
int main()
{
cin>>n>>usable;
for(int i=1; i<=n; i++)
{
cin>>value[i]>>cost[i];
}
// 初始化:计算只开采第一座矿的情况
for(int j=0; j<=usable; j++)
{
if(j >= cost[1])
{
dp[j] = value[1];
}
else
{
dp[j] = 0;
}
}
// 主循环
for(int i=2; i<=n; i++)
{
// 为了用上上一次主循环保存的值,应该倒着算
for(int j=usable; j>=1; j--)
{
// 如果人手足够
if(j >= cost[i])
{
//此时dp[x]保存的是上一次主循环的结果
// 即 用 x 人开采前 i-1 座矿的最大收益
int select = dp[j-cost[i]] + value[i];
int no_select = dp[j];
// 更新后dp[x]表示用 x 人开采前 i 座矿的最大收益
dp[j] = max(select, no_select);
}
// 如果人手不够,就不更新,值等于前 i-1 座矿的最大收益
}
}
// 打印最终的dp数组
cout<<"//-------------------------------------//"<<endl;
for(int j=0; j<=usable; j++)
{
cout<<dp[j]<<" ";
}
cout<<endl<<"//-------------------------------------//"<<endl;
cout<<dp[usable]<<endl;
return 0;
}
运行结果
这种优化对于一些可用资源(即背包最大容量)可变的题目的优化是非常大的,例如有 n 个物品,所有物品最大价值之和为 m,有k种可变的最大资源(即背包最大容量)
经典dp空间复杂度:n * m * k
优化后:m * k