DP动态规划_01背包问题

动态规划是一种思想, 因为很违背直觉, 所以初学时理解起来很困难, 和递归一样, 都是比较奇妙的思想.

01背包问题是各类背包问题(见背包九讲)中最简单的一种模型, 但是理解它也花了我很多时间.

网上和各种算法书籍都有关于背包问题的讲解.

刚做了两道基础的01背包问题, 遇到了几个问题, 也靠自己解决了它们

两道题:

1) 洛谷1048-采药
2) 洛谷1049-装箱问题

采药是一道裸的01背包问题, 按照常规的解法去做, 将模型对象一一对应即可

我遇到的一个问题是:

没有设置边界条件, 即物品体积为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背包问题

但不是裸的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];
}

So beautiful !!!

你可能感兴趣的:(DP,弃用的分类:c++,2017-2018寒假)