【笔记】《背包九讲》阅读笔记

01背包:

这是最基本的背包问题,每个物品最多只能放一次。

题目:有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。

状态转移方程 初始版:

for i=1..N
    for v=V..0
        f[v]=max{f[v],f[v-c[i]]+w[i]};

优化加函数化:ZeroOnePack,表示处理一件01背包中的物品,

procedure ZeroOnePack(cost,weight)
    for v=V..cost
        f[v]=max{f[v],f[v-cost]+weight}

此后01背包如下:

for i=1..N
    ZeroOnePack(c[i],w[i]);

注意事宜:

【是否完全装满背包】:初始化操作

1.要求“恰好装满背包”时的最优解,

除了f[0]为0,其它f[1..V]均设为-∞(这样就可以保证最终得到的f[N]是一种恰好装满背包的最优解)

2.不要求必须把背包装满。

将f[0..V]全部设为0。

【常数优化】

for i=1..n
    bound=max{V-sum{w[i..n]},c[i]}
    for v=V..bound

 

完全背包:

第二个基本的背包问题模型,每种物品可以放无限多次。

复杂度:O(VN)

for i=1..N
    for v=0..V
        f[v]=max{f[v],f[v-cost]+weight}

 

多重背包:

每种物品有一个固定的次数上限。

复杂度:O(V*Σlog n[i])

procedure MultiplePack(cost,weight,amount)
    if cost*amount>=V //转化为完全背包
        CompletePack(cost,weight) 
        return
    integer k=1
    while k<amount //拆分成log(amount)件01背包
        ZeroOnePack(k*cost,k*weight)
        amount=amount-k
        k=k*2
    ZeroOnePack(amount*cost,amount*weight)

 

混合背包:

将前面三种简单的问题叠加成较复杂的问题。

for i=1..N
    if 第i件物品属于01背包
        ZeroOnePack(c[i],w[i])
    else if 第i件物品属于完全背包
        CompletePack(c[i],w[i])
    else if 第i件物品属于多重背包
        MultiplePack(c[i],w[i],n[i])

 

二维费用的背包问题:

 

分组的背包问题:

有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。

这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。

求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

for 所有的组k
    for v=V..0
        for 所有的i属于组k
            f[v]=max{f[v],f[v-c[i]]+w[i]}

 

有依赖的背包问题:

i依赖于j,表示若选物品i,则必须选物品j

1、分为主/附件:

对主件i的“附件集合”先进行一次01背包,得到费用依次为0..V-c[i]所有这些值时相应的最大价值f'[0..V-c[i]]。

那么这个主件及它的附件集合相当于V-c[i]+1个物品的物品组,其中费用为c[i]+k的物品的价值为f'[k]+w[i]。

2、更一般的问题:

依赖关系以图论中“森林”的形式给出,限制只是每个物品最多只依赖于一个物品(只有一个主件)且不出现循环依赖。

事实上,这是一种树形DP,其特点是每个父节点都需要对它的各个儿子的属性进行一次DP以求得自己的相关属性。

泛化物品:

来自背包问题(背包九讲) - Code-dream - 博客园 (cnblogs.com)

 

考虑这样一种物品,它并没有固定的费用和价值,而是它的价值随着你分配给它的费用而变化。这就是泛化物品的概念。

更严格的定义之。在背包容量为V的背包问题中,泛化物品是一个定义域为0..V中的整数的函数h,当分配给它的费用为v时,能得到的价值就是h(v)。

这个定义有一点点抽象,另一种理解是一个泛化物品就是一个数组h[0..V],给它费用v,可得到价值h[V]。

 

一个费用为c价值为w的物品,如果它是01背包中的物品,那么把它看成泛化物品,它就是除了h(c)=w其它函数值都为0的一个函数。如果它是完全背包中的物品,那么它可以看成这样一个函数,仅当v被c整除时有h(v)=v/c*w,其它函数值均为0。如果它是多重背包中重复次数最多为n的物品,那么它对应的泛化物品的函数有h(v)=v/c*w仅当v被c整除且v/c<=n,其它情况函数值均为0。

一个物品组可以看作一个泛化物品h。对于一个0..V中的v,若物品组中不存在费用为v的的物品,则h(v)=0,否则h(v)为所有费用为v的物品的最大价值。P07中每个主件及其附件集合等价于一个物品组,自然也可看作一个泛化物品。

 

泛化物品的定义表明:在一个背包问题中,若将两个泛化物品代以它们的和,不影响问题的答案。

事实上,对于其中的物品都是泛化物品的背包问题,求它的答案的过程也就是求所有这些泛化物品之和的过程。设此和为s,则答案就是s[0..V]中的最大值。

 

背包问题问法的变化

【输出方案】

可以参照一般动态规划问题输出方案的方法:记录下每个状态的最优值是由状态转移方程的哪一项推出来的

再用一个数组g[i][v],设g[i][v]=0表示推出f[i][v]的值时是采用了方程的前一项(也即f[i][v]=f[i-1] [v]),g[i][v]表示采用了方程的后一项

i=N
v=V
while(i>0)
    if(g[i][v]==0)
        print "未选第i项物品"
    else if(g[i][v]==1)
        print "选了第i项物品"
        v=v-c[i]

【输出字典序最小的方案】

一般而言,求一个字典序最小的最优方案,只需要在转移时注意策略。

首先,子问题的定义要略改一些。我们注意到,如果存在一个选了物品1的最优方案, 那么答案一定包含物品1,原问题转化为一个背包容量为v-c[1],物品为2..N的子问题。反之,如果答案不包含物品1,则转化成背包容量仍为V,物品为2..N的子问题。

不管答案怎样,子问题的物品都是以i..N而非前所述的1..i的形式来定义的,所以状态的定义和转移方程都需要改一下。

但也许更简易的方法是先把物品逆序排列一下。在这种情况下,可以按照前面经典的状态转移方程来求值,只是输出方案的时候要注意:从N到1输入时,如果f[i][v]==f[i-1][i-v]及f[i][v]==f[i-1][f-c[i]]+w[i]同时成立,应该按照后者(即选择了物品i)来输出方案。

【求方案总数】

一般只需将状态转移方程中的max改成sum即可。例如若每件物品均是完全背包中的物品,转移方程即为

f[i][v]=sum{f[i-1][v],f[i][v-c[i]]}

【求第k优解】

第K优解则比求最优解的复杂度上多一个系数K。

其基本思想是将每个状态都表示成有序队列,将状态转移方程中的max/min转化成有序队列的合并。这里仍然以01背包为例讲解一下。

首先看01背包求最优解的状态转移方程:f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}。如果要求第K优解,那么状态f[i][v]就应该是一个大小为K的数组f[i][v][1..K]。其中f[i][v][k]表示前i个物品、背包大小为 v时,第k优解的值。“显然f[i][v][1..K]这K个数是由大到小排列的,所以我们把它认为是一个有序队列。

总的复杂度是O(VNK)。

另外还要注意题目对于“第K优解”的定义,将策略不同但权值相同的两个方案是看作同一个解还是不同的解。如果是前者,则维护有序队列时要保证队列里的数没有重复的。

 

背包问题的搜索解法

首先,可以从数据范围中得到命题人意图的线索。如果一个背包问题可以用DP解,V一定不能很大,否则O(VN)的算法无法承受,而一般的搜索解法都是仅与N有关,与V无关的。所以,V很大时(例如上百万),命题人的意图就应该是考察搜索。另一方面,N较大时(例如上百),命题人的意图就很有可能是考 察动态规划了。

 

posted @ 2022-02-02 02:28  心若笺诗  阅读(40)  评论(0编辑  收藏  举报