zoukankan      html  css  js  c++  java
  • 【算法复习】背包问题 经典动态规划

    其实我大概想法是想写一个动态规划的专题,然后里面可能有一些小专题,所以如果我没有鸽掉的话,这篇的链接会放入DP专题里面

    这里大概就写写常见的背包问题吧。

    01背包

    可以说是最经典的背包问题了,有(n)个物品, 每个物品有一个价值(vi)和一个体积(wi),背包的最多容纳体积之和为(m)的物体。
    现在让你求背包可以放入的最高价值之和。
    我们考虑令(dp[i][j])表示考虑到第i个物品,现在已经占用的背包容积是j的最优价值,那么我们有转移方程:
    (dp[i][j] = max{dp[i - 1][j - w[i]] + v[i]})
    伪代码:

    for i 1 to n
      for j 1 to m
        dp[i][j] = max{dp[i - 1][j - w[i]] + v[i]}
    

    滚动数组优化

    可以发现(dp[i])一定从(dp[i - 1])转移,而且我们只需要最后的dp[n],也就是考虑完所有物品的数组.所以我们并不需要保留所有的dp数组,只需要保留上一组的信息就可以算出下一组,
    因此我们令(f[i])表示已经占用的背包容积是j的最优价值。
    伪代码:

    for i 1 to n
      for j m to 1//注意这里
        dp[j] = max{dp[j - w[i]] + v[i]}
    

    这里注意一个细节,你可以发现j这个循环的循环顺序被反过来了,为什么呢?
    考虑到w[i]为正数,因此(j - w[i] < j),而我们的dp数组是需要用上一组的信息转移出下一组,因此我们需要保证,求dp[j]用到的转移状态是上一组的状态。
    而求dp[j]时会用到比j小的状态,因此我们需要保证在dp[j]时,比j小的状态还没有被更新,因此我们用逆序枚举。
    那么为什么我们必须用上一组的信息转移出下一组呢?
    我们考虑如果我们用当前组转移当前组会发生什么。
    可以发现,如果我们用当前组更新当前组,那就以为着用于更新的状态里已经考虑过当前物品了,这个时候你再加一次当前物品,相当于考虑了多次当前物品,不符合每个物品只能用一次的条件。

    完全背包

    (n)种物品, 每种物品有一个价值(vi)和一个体积(wi),且数量为无限个,背包的最多容纳体积之和为(m)的物体。
    现在让你求背包可以放入的最高价值之和。
    从01背包的滚动数组优化的细节解释中,可以发现,其实我们顺序枚举,虽然不满足01背包的条件限制,但是却刚好满足完全背包的条件!
    所以我们只需要将j顺序枚举,就是完全背包的做法。
    伪代码:

    for i 1 to n
      for j 1 to m//注意这里
        dp[j] = max{dp[j - w[i]] + v[i]}
    

    多重背包

    (n)种物品, 每种物品有一个价值(vi)和一个体积(wi),且数量为(ni)个,背包的最多容纳体积之和为(m)的物体。
    现在让你求背包可以放入的最高价值之和。
    有2个思路,1个是把有(ni)个的第i种物品,拆分成(ni)个,当01背包做,1个是多一层循环,枚举第i个物品放进去了多少个。
    复杂度都是(sum{ni} cdot m)的。
    其实这2种思路本质上差不多,我们考虑二进制拆分进行分组来优化。
    对于第i个物品,其实我们只需要对(ni)进行拆分,使得拆分出来的几个组,对于任意(0<= x <= ni),都有至少一种取舍方案使得第i种物品一共取走了x个.
    要实现这个目标,二进制拆分是一个很好的选择。
    我们先找到一个最大的k使得(2^k<=ni),然后令(t = ni - 2^k),易知(t < 2^k)
    然后我们对(2^k)进行拆分,拆成(2^0 + 2^1 + 2^3 ... + 2^{k - 1} + 1),容易证明这k个数可以组合出0到(2^k)中任意一个数。
    然后对于剩下的t我们单独作为一组,由于(t < 2^k),所以之前的k个数可以组合出0到t中任意一个数,而(2^{k} + 1)(ni)中的任意一个数可以由t的这一组和之前的k组组合得到(你可以理解为取走所有组,然后我们需要删去一个0到t中的一个数来得到(2^{k} + 1)(ni)中的某个数,而这个数肯定能被前k个数凑出)

    ------以上是最基础的三种背包问题-----下面会写一些扩展的背包问题(大概就是差不多的问题+一点点改动)-------有时间就更------

    恰好装满

    其实可以直接套01背包的做法,如果数据没有价值为0和负数的话(
    然后看dp[m]有没有值就行。
    如果有价值为0和负数……那就稍微改一下,令dp[i]表示考虑塞入体积之和为i,能否实现。
    然后初始化一下,dp[0] = 0, 其他都是-inf,
    最后看dp[m]如果还是-inf的话,说明不能恰好装满,如果不是-inf的话那dp[m]就是答案

    多个限制

    直接dp数组加维就行

    求方案数

    改一下dp方程
    以01背包为例:(dp[j] += sum{dp[j - w[i]] + v[i]}),注意这里是+=,初始的dp[j]表示的是不放入当前物品,
    完全背包思路类似

    本文不允许商业性使用,个人转载请注明出处! 知识共享许可协议
    本作品采用知识共享署名-非商业性使用-禁止演绎 3.0 未本地化版本许可协议进行许可。
  • 相关阅读:
    linux获取日志指定行数范围内的内容
    python解决open()函数、xlrd.open_workbook()函数文件名包含中文,sheet名包含中文报错的问题
    robot framework添加库注意事项
    robot framework取出列表子元素
    Vue 及框架响应式系统原理
    响应式布局和自适应布局的不同
    前端综合学习笔记---异步、ES6/7、Module、Promise同步 vs 异步
    前端综合学习笔记---变量类型、原型链、作用域和闭包
    doT.js模板引擎及基础原理
    Spring Boot入门第五天:使用JSP
  • 原文地址:https://www.cnblogs.com/ww3113306/p/15232324.html
Copyright © 2011-2022 走看看