动态规划是一种思想, 因为很违背直觉, 所以初学时理解起来很困难, 和递归一样, 都是比较奇妙的思想.
01背包问题是各类背包问题(见背包九讲)中最简单的一种模型, 但是理解它也花了我很多时间.
网上和各种算法书籍都有关于背包问题的讲解.
刚做了两道基础的01背包问题, 遇到了几个问题, 也靠自己解决了它们
两道题:
1) 洛谷1048-采药
2) 洛谷1049-装箱问题
我遇到的一个问题是:
没有设置边界条件, 即物品体积为71而包体积为70时没有跳过, 而是用了71, 导致结果更大
看了一遍书之后, 理解了然后改正了错误
#include
using namespace std;
int main()
{
int vv, k, time[105] = {}, val[105] = {}, dp[105][1005] = {};
cin >> vv >> k;
for(int i = 1; i <= k; ++i) {
cin >> time[i] >> val[i];
}
for(int i = 1; i <= k; ++i) {
for(int j = 1; j <= vv; ++j) {
if(time[i] > j) { //先前这里时没有的
dp[i][j] = dp[i - 1][j];
continue;
}
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - time[i]] + val[i]);
}
}
cout << dp[k][vv];
}
但不是裸的01背包问题
它是求最小剩余容量|min
初看时, 以为是贪心问题, 但自习分析一下, 发现还是01背包问题
稍微变了下型
我推导状态转移方程花了不少时间, 先是推出了这样一个错误的方程
dp[i][j] = min{dp[i][dp[i-1][j]], dp[i-1][j]}; //注意括号
我也不太记得我当时是怎么想的
再画了个表格手动模拟了一下dp的过程, 发现, 其实方程还是类似的, 不过少了+val[i]
dp[i][j] = min{dp[i][j - v[i]], dp[i-1][j]}; //求最小的
新的状态转移方程更难理解, 不过话说回来, dp本来就是难理解的东西
不过, 这两道题的本质是一样的, 都是01背包问题的本质(这不是废话吗…)
什么本质呢?
第i个木箱有两种情况, 选或不选. 选的时候选上了(分配相同大小的容积给它, 相当于消去了), (即0+)再加剩余容积的最小剩余容积(之前计算过). 或者不选, 那么最小的剩余容积还是上一行同一列的状态. 然后取选和不选中剩余容积最小的状态. 这就是当前最优状态, 也是对整道题目来说的最优子结构
我认为, 本质就是在选和不选两种状态中选择更优的状态进行状态转移, 这样就得到了最优状态
可以肯定的是, 做好背包问题的关键是: 写好状态转移方程. 而要写好状态转移方程, 就要提取问题信息, 将它模型化.
目前我总结出来的方法是, 做表格, 从表格上分析状态
下面是第二题的AC代码
#include
#include
using namespace std;
int dp[31][20005];
int main()
{
int vmax, n; //vmax为总体积, n为箱子个数
cin >> vmax >> n;
for(int i = 0; i <= vmax; ++i) {
dp[0][i] = i; //没有箱子时, 剩余的空间为总空间
}
for(int i = 0; i <= n; ++i) {
dp[i][0] = 0; //有箱子但没有体积时, 剩余空间为0
}
int *a = new int [n + 1];
for(int i = 1; i <= n; ++i) {
cin >> a[i];
}
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= vmax; ++j) {
if(a[i] > j) {
dp[i][j] = dp[i - 1][j];
continue;
}
dp[i][j] = min(dp[i - 1][j - a[i]], dp[i - 1][j]);
}
}
cout << dp[n][vmax];
}
2018-2-12更新
在博客上看了一些其他人的代码, 再写了几遍, 加深了一些理解.
出现了一个错了好几次的错误
把物体体积和物体价值搞错!!! 难道是命名出了问题, 容易混淆?*
以后千万别搞错了!!!
做了一些优化, 通过压维节省了大量的空间, 不过这样的话计算最有子结构就要逆序了. 因为每个计算要用到前面解决的问题而不是后面, 所以前面的结果不能够被覆盖掉.
不过, 其实还是可以正序的, 只是要开2*vv大小的空间.
裸的01背包问题的代码其实是非常简洁而且非常优美的.
代码虽短, 但理解不易.
#include
using namespace std;
int v[105] = {}, w[105] = {}, vv, k, dp[1005] = {};
int main()
{
cin >> vv >> k;
for(int i = 1; i <= k; ++i)
cin >> w[i] >> v[i];
for(int i = 1; i <= k; ++i) {
for(int j = vv; j >= w[i]; --j) {
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
cout << dp[vv];
}