• 背包类型题目总结


    背包问题九讲:https://github.com/tianyicui/pack

    1、0-1背包问题

    有N 件物品和一个容量为V 的背包。放入第i 件物品耗费的费用是Ci,得到的价值是Wi。求解将哪些物品装入背包可使价值总和最大。

    每个物品仅有一件,可以选择放或者不放。

    F[i; v] 表示前i件物品恰放入一个容量为v 的背包可以获得的最大价值。

    状态转移方程:

    F[i; v] = max{F[i - 1 ; v], F[i - 1; v - Ci] +Wi}

    “将前i 件物品放入容量为v 的背包中”这个子问题,若只考虑第i 件物品的策略(放或不放),那么就可以转化为一个只和前i - 1 件物品相关的问题。如果不放第i 件物品,那么问题就转化为“前i - 1 件物品放入容量为v 的背包中”,价值为F[i - 1; v];如果放第i 件物品,那么问题就转化为“前i - 1 件物品放入剩下的容量为v - Ci 的背包中”,此时能获得的最大价值就是F[i - 1; v - Ci] 再加上通过放入第i 件物品获得的价值Wi。

    优化空间复杂度:

    滚动数组,F[i; v]只与i - 1一行有关。可以让F[i]与F[i-1]存在同一个数组之中,安装V~Ci的顺序求解,那么只需要一个数组。

    初始化的细节问题:

    有的题目要求“恰好装满背包”时的最优解,有的题目则并没有要求必须把背包装满。

    如果是第一种问法,要求恰好装满背包,那么在初始化时除了F[0] 为0,其它F[1::V ] 均设为-∞,这样就可以保证最终得到的F[V ] 是一种恰好装满背包的最优解。如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将F[0::V ]全部设为0。

    这是为什么呢?可以这样理解:初始化的F 数组事实上就是在没有任何物品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量为0 的背包可以在什么也不装且价值为0 的情况下被“恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态,应该被赋值为-∞ 了。如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为0,所以初始时状态的值也就全部为0了。

    小结:

    01 背包问题是最基本的背包问题,它包含了背包问题中设计状态、方程的最基本思想。另外,别的类型的背包问题往往也可以转换成01 背包问题求解。故一定要仔细体会上面基本思路的得出方法,状态转移方程的意义,以及空间复杂度怎样被优化。

    lintcode 背包问题I

     1 class Solution:
     2     # @param m: An integer m denotes the size of a backpack
     3     # @param A: Given n items with size A[i]
     4     # @return: The maximum size
     5     def backPack(self, m, A):
     6         # write your code here
     7         if not m or not A:
     8             return 0
     9             
    10         dp = [0 for i in xrange(m + 1)]
    11         
    12         for i in xrange(1, len(A) + 1):
    13             bound = max(m - sum(A), A[i - 1])
    14             for j in xrange(m, bound - 1, -1):
    15                 dp[j] = max(dp[j], dp[j - A[i - 1]] + A[i - 1])
    16                 
    17         return dp[-1]
    View Code

    lintcode 背包问题II

     1 class Solution:
     2     # @param m: An integer m denotes the size of a backpack
     3     # @param A & V: Given n items with size A[i] and value V[i]
     4     # @return: The maximum value
     5     def backPackII(self, m, A, V):
     6         # write your code here
     7         dp = [0] * (m + 1)
     8         for i in xrange(len(A)):
     9             for v in xrange(m, A[i] - 1, -1):
    10                 dp[v] = max(dp[v], dp[v - A[i]] + V[i])
    11                 
    12         return dp[-1]
    View Code

    2、完全背包问题

    有N 种物品和一个容量为V 的背包,每种物品都有无限件可用。放入第i 种物品的费用是Ci,价值是Wi。求解:将哪些物品装入背包,可使这些物品的耗费的费用总和不超过背包容量,且价值总和最大。

    与0-1背包问题,不同的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取0 件、取1 件、取2件……直至取⌊V /Ci⌋ 件等许多种。

    状态转移方程:

    F[i; v] = max{F[i - 1; v - kCi] + kWi | 0 <= kCi <= v}

    这跟01 背包问题一样有O(V N) 个状态需要求解,但求解每个状态的时间已经不是常数了,求解状态F[i; v] 的时间是O(v/Ci)。

    一个优化:

    完全背包问题有一个很简单有效的优化,是这样的:若两件物品i、j 满足Ci <= Cj且Wi >= Wj,则将可以将物品j直接去掉,不用考虑。

    首先将费用大于V 的物品去掉,然后使用类似计数排序的做法,计算出费用相同的物品中价值最高的是哪个,可以O(V + N) 地完成这个优化。这个不太重要的过程就不给出伪代码了,希望你能独立思考写出伪代码或程序。

    转换成0-1背包问题求解:

    思路1:将第i件物品,转化为V/Ci个,价值不变的物品,没有简化时间复杂度。

    思路2:把第i 种物品拆成费用为Ci*2^k、价值为Wi*2^k 的若干件物品,其中k 取遍满足Ci*2^k ~ V 的非负整数。二进制的思想。因为,不管最优策略选几件第i 种物品,其件数写成二进制后,总可以表示成若干个2k 件物品的和。

    O(VN)的解法:

    变换状态转移方程的形式:

    F[i; v] = max(F[i - 1; v]; F[i; v - Ci] +Wi)

    翻转0-1背包问题中,遍历V~Ci的次序即可。

    3、多重背包问题

    有N 种物品和一个容量为V 的背包。第i 种物品最多有Mi 件可用,每件耗费的空间是Ci,价值是Wi。求解将哪些物品装入背包可使这些物品的耗费的空间总和不超过背包容量,且价值总和最大。

    状态转移方程:

    因为对于第i 种物品有Mi + 1 种策略:取0 件,取1 件……取Mi 件。令F[i; v]表示前i 种物品恰放入一个容量为v 的背包的最大价值,则有状态转移方程:

    F[i,v] = max{F[i - 1; v - k * Ci] + k * Wi | 0 <= k <= Mi}

    转换为0-1背包问题:

    思路1:转换为Mi个价值不变的物品,复杂度不变

    思路2:转换为一组使用二进制数字加权费用和价值的物品,二进制数字取1,2,4……2^(k-1),其中k是Mi - 2^k +1>0的最大k

    4、混合三种背包问题

    如果将前面1、2、3中的三种背包问题混合起来。也就是说,有的物品只可以取一次(01 背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。应该怎么求解呢?

    0-1背包与完全背包的混合:

    如果只有两类物品:一类物品只能取一次,另一类物品可以取无限次,那么只需在对每个物品应用转移方程时,根据物品的类别选用顺序或逆序的循环即可,复杂度是O(V N)。伪代码如下:

    再加上多重背包:

    可以将多重背包的物品分割成logMi个,再利用0-1背包和完全背包的混合。

    5、二维背包的费用问题

    二维费用的背包问题是指:对于每件物品,具有两种不同的费用,选择这件物品必须同时付出这两种费用。对于每种费用都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。设第i 件物品所需的两种费用分别为Ci 和Di。两种费用可付出的最大值(也即两种背包容量)分别为V 和U。物品的价值为Wi。

    状态转移方程:

    费用加了一维,只需状态也加一维即可。

    F[i; v; u] = max{F[i - 1; v; u]; F[i - 1; v - Ci; u - Di] + Wi}

    物品总个数的限制:

    有时,“二维费用”的条件是以这样一种隐含的方式给出的:最多只能取U 件物品。这事实上相当于每件物品多了一种“件数”的费用,每个物品的件数费用均为1,可以付出的最大件数费用为U。

    小结:

    当发现由熟悉的动态规划题目变形得来的题目时,在原来的状态中加一维以满足新的限制是一种比较通用的方法。希望你能从本讲中初步体会到这种方法。

    6、分组的背包问题

    有N 件物品和一个容量为V 的背包。第i 件物品的费用是Ci,价值是Wi。这些物品被划分为K 组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

    状态转移方程:

    这个问题变成了每组物品有若干种策略:是选择本组的某一件,还是一件都不选。也就是说设F[k; v] 表示前k 组物品花费费用v 能取得的最大权值,则有:

    F[k; v] = max{F[k - 1; v]; F[k - 1; v - Ci] +Wi | item i in group k}

    小结:

    分组的背包问题将彼此互斥的若干物品称为一个组,这建立了一个很好的模型。不少背包问题的变形都可以转化为分组的背包问题(例如7),由分组的背包问题进一步可定义“泛化物品”的概念,十分有利于解题。

    7、有依赖的背包问题

    这种背包问题的物品间存在某种“依赖”的关系。也就是说,物品i 依赖于物品j,表示若选物品i,则必须选物品j。为了简化起见,我们先设没有某个物品既依赖于别的物品,又被别的物品所依赖;另外,没有某件物品同时依赖多件物品。

    8、泛化物品

    9、背包问题的各种变化

    以上涉及的各种背包问题都是要求在背包容量(费用)的限制下求可以取到的最大价值,但背包问题还有很多种灵活的问法,在这里值得提一下。但是我认为,只要深入理解了求背包问题最大价值的方法,即使问法变化了,也是不难想出算法的。例如,求解最多可以放多少件物品或者最多可以装满多少背包的空间。这都可以根据具体问题利用前面的方程求出所有状态的值(F 数组)之后得到。还有,如果要求的是“总价值最小”“总件数最小”,只需将状态转移方程中的max改成min 即可。下面说一些变化更大的问法。

    输出方案:

    如果要求输出这个最优值的方案,可以参照一般动态规划问题输出方案的方法:记录下每个状态的最优值是由状态转移方程的哪一项推出来的,换句话说,记录下它是由哪一个策略推出来的。便可根据这条策略找到上一个状态,从上一个状态接着向前推即可。

  • 相关阅读:
    关系数据理论
    JavaScript语言——对象
    网络编程基础入门级
    数据库加快查询速度索引
    C/C++随机函数的生成(转载)
    sql连接查询
    深入浅出HTTP请求
    17搜索如何抓全网页
    搜索引擎之百度一下
    搜索引擎之中搜
  • 原文地址:https://www.cnblogs.com/zcy-backend/p/6851741.html
Copyright © 2020-2023  润新知