背包问题九讲学习小记

前言

有些大佬小学就啃完背包问题九讲了,%%%%%。
细节落实要细致。

原目录(大致意思)

1 01背包
2完全背包
3多重背包
4 123讲的综合
5二维费用的背包问题
6分组背包
7依赖性背包
8泛化物品
9一些变式

理清文章思路

先呈上2张概念图表。
背包问题九讲学习小记_第1张图片
解释此图。
背包问题是DP问题中的一种。问题的模型是,将一些物品(有序地/无序地)放入有容量的背包,然后问最大价值,或者其他问题。
物品的概念:一般的物品,有固定的体积、价值;泛化物品,就是物品的容量和价值是不固定的,换句话说,物品的价值随它的容量而变化。泛化物品能够表示所有的物品。
背包有如下几种:
基础的:01背包,多重背包,完全背包。实际上将多重背包,完全背包的物品进行拆分后,都是01背包。
综合的:
分组背包。每组物品只能够选其中的0件或1件。
01,多重,完全背包的混合(解决问题的方法:只需判断该物品属于哪种背包即可)。
二维费用背包。就是背包的体积是二维的,对应地,还可以理解为花费是二维的。
依赖性背包:就是树形背包。
变式:学会了这一部分的问题能够解决其他的一些DP问题。

通过解决背包问题,我学会了解决某些DP的技巧。
空间上,时间上,还有搜索顺序上的。
时间的优化是最主要的,通过 O ( V ) O(V) O(V)的时间去重,让每种容量的物品只保留最高价值的。
通过 O ( K ) O(K) O(K)的时间选取最优值,也是一种巧妙的技巧。我记得,NOIP2016蚯蚓的那题的解题思路的基本原理跟这个相差无几。

这两张图表中的知识似乎没有联系?
看下面的图。
背包问题九讲学习小记_第2张图片
按照箭头的颜色以及编号,先谈对这些优化的理解。
优化1,如果01背包的空间减小一维,那么枚举容量的时候需倒序。
优化2,通过合并物品,在DP中枚举物品的件数,转化为01背包问题。(描述得有点不清楚)
优化3,单调队列优化,通过改变枚举背包容量的方式,将 O ( V ∗ ∑ m [ i ] ) O(V*\sum m[i]) O(Vm[i])的复杂度将至 O ( V N ) O(VN) O(VN)
优化4,将 m m m个物品的限制,可以拆成 l o g log log个物品,从而表达出 0... m [ i ] 0...m[i] 0...m[i]这些物品的数量。
优化5,从小到大枚举容量。
优化6,见优化2。
优化7,见优化2。
优化8,见优化5。
优化9,见优化4。
优化10,选择附件的方式能够将一些有冲突的物品归为一组。广泛地,选出一些有冲突的物品,归为一组。做分组背包问题。
优化11,通过 O ( K ) O(K) O(K)的时间选取最优值,也是一种巧妙的技巧。
优化12,改变枚举物品的方式,比如按照编号从大到小枚举。
优化原理:DP状态及他们之间的转移关系可视为DAG,寻找最优方案的时候,记录的是目前的最优值通过走DAG中的哪条边得来。这能够应用于寻找字典序最小最优解。
优化13,基于DFS序的背包解决树形依赖背包。记住,在DP的时候,枚举DFS序究竟是顺序的,还是倒序的,这个要很清楚。
优化14,让相同容量的物品,只保留价值最大的那一个。这可以应用到所有的背包问题中。
然而这也只是DP的解决方案中的冰山一角,下面就是要学的内容。

要学什么

前3讲分别讲得是01背包,完全背包以及多重背包。
后面6讲讲得是综合性的问题。
看完这9讲之后,尝试对各种背包问题归类。
最重要地,背包问题要与其他DP问题联系起来

开始口胡前三讲

一般地,设背包容量为 C C C,设每个物品的体积为 v [ i ] v[i] v[i],每个物品的价值为 w [ i ] w[i] w[i]
对于01背包: 每种物品最多只有1件。

for i=1 to n
    for j=C to v[i]
        f[j]=max(f[j-v[i]]+w[i])

时间复杂度 O ( n 2 ) O(n^2) O(n2)
空间复杂度 O ( n ) O(n) O(n)

对于完全背包(无限背包)
每个物品有无限件。
实际上每件物品最多只有 ⌊ C v [ i ] ⌋ \lfloor\frac{C}{v[i]}\rfloor v[i]C件。
没有优化的:
f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 1 ] [ j − k ∗ v [ i ] ] + k ∗ w [ i ] ) f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]) f[i][j]=max(f[i][j],f[i1][jkv[i]]+kw[i])
优化①,如果 w [ i ] ≤ w [ j ] , v [ i ] ≥ v [ j ] w[i]≤w[j],v[i]≥v[j] w[i]w[j],v[i]v[j],显然将物品 j j j删除。
但这个优化没优化多少。
优化②,将 ⌊ C v [ i ] ⌋ \lfloor\frac{C}{v[i]}\rfloor v[i]C转化为二进制。
比如 ⌊ C v [ i ] ⌋ = 13 \lfloor\frac{C}{v[i]}\rfloor=13 v[i]C=13,拆成 1 , 4 , 8 1,4,8 1,4,8
则问题转化为01背包。
这个优化使效率提高了不少。
优化③
将代码改为:

for i=1 to n
    for j=v[i] to C
        f[j]=max(f[j-v[i]]+w[i])

为什么对?
因为j正着循环,考虑了物品i放 [ 1 , ⌊ C v [ i ] ⌋ ] [1,\lfloor\frac{C}{v[i]}\rfloor] [1,v[i]C]个的情况。

多重背包:第i种物品最多有 m [ i ] m[i] m[i]件。
问题的转化:转化为01背包和完全背包问题。
比如 m [ i ] = 13 m[i]=13 m[i]=13,拆成 1 , 4 , 8 1,4,8 1,4,8
如果 m [ i ] ≥ ⌊ C v [ i ] ⌋ m[i]\geq\lfloor\frac{C}{v[i]}\rfloor m[i]v[i]C,则第i种物品归属完全背包。
否则归属01背包。
时间复杂度: O ( C ∗ ∑ m [ i ] ) O(C*\sum m[i]) O(Cm[i])
当然,更优的做法:
希望用单调队列来解决此问题。
优化原理:取模的思想。
f [ j ] = m a x ( f [ j − k ∗ v [ i ] ] + k ∗ w [ i ] ) f[j]=max(f[j-k*v[i]]+k*w[i]) f[j]=max(f[jkv[i]]+kw[i])
j可以看成 k ∗ v [ i ] + b k*v[i]+b kv[i]+b
考虑另外一种枚举的方式。
枚举b(余数)
现在要求 ( f [ k ∗ v [ i ] + b ] − k ∗ w [ i ] ) (f[k*v[i]+b]-k*w[i]) (f[kv[i]+b]kw[i])的最大值。
首先, f [ k ∗ v [ i ] + b ] = m a x ( f [ ( a − k ) v [ i ] + b ] + k ∗ w [ i ] ) f[k*v[i]+b]=max(f[(a-k)v[i]+b]+k*w[i]) f[kv[i]+b]=max(f[(ak)v[i]+b]+kw[i])
k ′ = a − k k'=a-k k=ak
原方程可化为 f [ k ∗ v [ i ] + b ] = m a x ( f [ k ′ ∗ w [ i ] + b ] − k ′ ∗ w [ i ] + a ∗ w [ i ] ) f[k*v[i]+b]=max(f[k'*w[i]+b]-k'*w[i]+a*w[i]) f[kv[i]+b]=max(f[kw[i]+b]kw[i]+aw[i])
观察式子, a ∗ w [ i ] a*w[i] aw[i]是不变的,而 f [ k ′ ∗ w [ i ] + b ] − k ′ ∗ w [ i ] f[k'*w[i]+b]-k'*w[i] f[kw[i]+b]kw[i]可以看成 k ′ k' k的函数。
所以, F ( a ) = m i n { F ( k ′ ) } + a ∗ w [ i ] F(a)=min\{F(k')\}+a*w[i] F(a)=min{F(k)}+aw[i]
经典的单调队列优化,解决此问题。
时间复杂度: O ( n C ) O(nC) O(nC)

背包综合

前三讲背包综合

01,完全,多重背包的混合?
将困难的问题转化成一个个简单的问题。
判断出哪些物品归属01/完全/多重背包即可。

for i=1 to n
    if 第i件物品∈01背包 ...
    else
    if 第i件物品∈完全背包 ...
    else
    if 第i件物品∈多重背包 ...

这3类背包的综合应用题实际上是很多的。

二维费用背包问题

对于物品i,有两种类型的代价,选择物品i的时候,同时要花费这两种代价。
f [ i ] [ u ] [ v ] = m a x { f [ i − 1 ] [ u ] [ v ] , f [ i − 1 ] [ u − c 1 [ i ] ] [ v − c 2 [ i ] ] + w [ i ] } f[i][u][v]=max\{f[i-1][u][v],f[i-1][u-c_1[i]][v-c_2[i]]+w[i]\} f[i][u][v]=max{f[i1][u][v],f[i1][uc1[i]][vc2[i]]+w[i]}
可以将空间的复杂度优化一下。
f [ u ] [ v ] = m a x { f [ u ] [ v ] , f [ u − c 1 [ i ] ] [ v − c 2 [ i ] ] + w [ i ] } f[u][v]=max\{f[u][v],f[u-c_1[i]][v-c_2[i]]+w[i]\} f[u][v]=max{f[u][v],f[uc1[i]][vc2[i]]+w[i]}

挖掘题目隐含条件

二维费用,这种条件在题目中会被很隐含地给出来。
看这道例题。
TOJ3596
有N张光盘,每张光盘有一个价钱 c [ i ] c[i] c[i],现在要从N张光盘中买M张,预算为L,每张光盘有一个快乐值 w [ i ] w[i] w[i],要求在不超过预算并且恰好买M张,使得快乐值最大。
如果只问不超过m张,那么很好办。
但是如果同时要满足买M张,那么必须开多一维。
题目中有一个隐含的条件:购买的光盘数量是第二维代价。(每张光盘的代价为1)
f [ u ] [ v ] = m i n ( f [ u ] [ v ] , f [ u − 1 ] [ v − c [ i ] ] + w [ i ] ) f[u][v]=min(f[u][v],f[u-1][v-c[i]]+w[i]) f[u][v]=min(f[u][v],f[u1][vc[i]]+w[i])

涉及复整数域的问题

问题
背包的容量和物品的费用都是复整数。
跟一维背包没有太大区别,只不过同时维护两维信息罢了。
换句话说,只不过是数域发生了改变,其他什么的没有变化。

思想

增加一维状态满足新限制。

分组背包

问题:有n件物品,背包容量为C。
第i件物品体积为 v [ i ] v[i] v[i],利益为 w [ i ] w[i] w[i]
将n件物品分为K组,每组物品最多选一件。
求所装物品的体积和不超过C的情况下,最大价值和。

解题思路

每组选0件还是选1件。
所以可以得出式子
f [ k ] [ j ] = m a x ( f [ k − 1 ] [ j ] , f [ k − 1 ] [ j − v [ i ] ] + w [ i ] ∣ i ∈ 第 k 组 物 品 ) f[k][j]=max(f[k-1][j],f[k-1][j-v[i]]+w[i]|i∈第k组物品) f[k][j]=max(f[k1][j],f[k1][jv[i]]+w[i]ik)
如果空间需要优化,那么关于花费的状态需倒序枚举。
优化后的伪代码:

for k=1 to K
    for j=C downto min(v[i],i∈第k组)
        for i∈第k组
            f[j]=max(f[k-1][j],f[k-1][j-v[i]]+w[i]);

还有优化?
如果 w [ i ] ≤ w [ j ] , v [ i ] ≥ v [ j ] w[i]≤w[j],v[i]≥v[j] w[i]w[j],v[i]v[j],显然将物品 j j j删除。
时间复杂度: O ( V + N ) O(V+N) O(V+N)
V是总体积。N是物品个数。
去掉log的方法,就是计数排序(桶排)
提示:体积为 v 1 v_1 v1中,价值最大的物品是已知的。

依赖性背包问题

原条件跟背包问题前3讲差不多。
只不过增加了一个条件。
选择了物品i,需要先选择物品j。
NOIP2006金明的预算方案
选择一个主件,每个主件可以选择若干个附件。
问题该向哪个方向转化?
转化为分组背包问题。

依照什么分组?
每个主件可以选择它的附件,这些附件随便选。(设主件k有a[k]个附件)
对于主件k,有 2 a [ k ] 2^{a[k]} 2a[k]种选择方案,每种选择方案可以看作是一件物品。
这些物品是同一组的,所以用分组背包解决。
问题来了。
每组的物品很多。
考虑将物品的数量变为 O ( V ) O(V) O(V)个。用 O ( V + N ) O(V+N) O(V+N)的去重方法即可。
然后复杂度变成 O ( n V ) O(nV) O(nV)了。
和图的联系:
相当于一片森林,深度的最大值为1。(根深度为0)
如果深度的最大值>1,那么问题该如何解决?
此时引入第8讲,泛化物品的概念。
问题会在后文给出答案。

泛化物品

一个物品,无固定的费用与价值。
它的价值随它被分配的费用而变化
从特殊到一般的思想
01背包,完全背包,多重背包就是一些特殊的情况。
物品i的价值是一个函数 h i ( v ) h_i(v) hi(v)
01中背包物品i的函数:
h i ( v ) = { w [ i ] v=v[i] 0 others h_i(v)= \begin{cases} w[i]& \text{v=v[i]}\\ 0& \text{others} \end{cases} hi(v)={w[i]0v=v[i]others
多重背包的物品i的函数
h i ( v ) = w [ i ] ∗ v , v ∈ [ 0 , m [ i ] ] h_i(v)=w[i]*v,v∈[0,m[i]] hi(v)=w[i]v,v[0,m[i]]
完全背包的物品i的函数
h i ( v ) = w [ i ] ∗ v , v ∈ [ 0 , ⌊ C v [ i ] ⌋ ] h_i(v)=w[i]*v,v∈[0,\lfloor\frac{C}{v[i]}\rfloor] hi(v)=w[i]v,v[0,v[i]C]
一个泛化物品组h,它的价值用函数表示如下:
h ( v ) h(v) h(v)为体积为v的物品的最大价值。
如果这些物品的体积都没有v,则 h ( v ) = 0 h(v)=0 h(v)=0

泛化物品的DP

目标:求解一个泛化物品f的关于物品体积的函数 f ( v ) f(v) f(v)
如果f是泛化物品f1与f2的和,则
f ( v ) = m a x { f 1 ( v 1 ) + f 2 ( v − v 1 ) } , v 1 ∈ [ 0 , v ] f(v)=max\{f1(v_1)+f2(v-v_1)\},v_1∈[0,v] f(v)=max{f1(v1)+f2(vv1)},v1[0,v]

总而言之,可以通过一系列的泛化物品的加和操作,得到最终问题的解。
在《背包九讲2.0》中,有这么一句好话:

一个背包问题中,可能会给出很多条件,包括每种物品的费用、价值等属性,物品之间的分组、依赖等关系等。但肯定能将问题对应于某个泛化物品。


我的理解:
物品的定义已由特殊到一般。泛化物品已经能够表示所有的物品,这个过程相当于给物品一个新的定义。

泛化物品的DP,最关键的步骤

通过DP来合并出一个新的泛化物品。与**DP的最优子结构**有很大的联系:通过DP来使得目前代表泛化物品的函数记录着最优值。 ### 回到之前的问题 如果深度的最大值>1,那么问题该如何解决? (也就是附件也有它的附件集合) 显然动用树形背包。 处理父节点的信息之前,就应该处理好它子树的信息。 换句话说,泛化物品x就是它的子树的泛化物品之和。 问题来了。复杂度怎么优化。这种赤裸裸的算法是$O(n^3)$的。 显然的优化:基于DFS序的DP。 这个优化基于DP的最优子结构性质。 求出DFS序,$f[i][j]=max\{f[i+1][j-v[x]]+w[x],f[i+siz[x]][j]\}$。 其中,x是DFS序中第i个元素。 这样就能够$O(n^2)$解决问题了。 推荐做如下的一道题目:[请转此处](https://blog.csdn.net/Psaily/article/details/81951022) 这就是树形依赖背包的一个最简单的版本。 ## 一些变式 将变式问题特意分出一个单元来讲。 ### 变式0 求解最多可以放多少件物品。 解法很显然。 ### 变式1 求具体方案。 解法原理:需要求出每一个状态$f[i][v]$究竟从何处转移过来。 (DP状态之间的转移关系可以看成是一个DAG) 以01背包为例,$f[i][v]=max\{f[i-1][v],f[i-1][v-c[i]]+w[i]\}$ 判断$f[i][v]$的值是等式右边的2个值的哪一个即可。

空间优化后,可以写如下写法:
g [ v ] [ 0 ] g[v][0] g[v][0]表示 v − c [ g [ v ] [ 1 ] ] v-c[g[v][1]] vc[g[v][1]] g [ v ] [ 1 ] g[v][1] g[v][1]表示最后装了哪个物品,使得背包容积变成 v v v

i=n;
v=V';
while(v){
    选择物品g[v][1]
    v=g[v][0];
}

变式2

字典序最小
也就是说,需要给出 1.. n 1..n 1..n号物品中求出最优解,且字典序最小。
先举01背包为例。
考虑目前处理的问题是什么
设目前需要解决的问题是 s o l v e ( V , 1.. n ) solve(V,1..n) solve(V,1..n)
假设最优解中含有1号物品,那么问题转化成 s o l v e ( V − c 1 , 2.. n ) solve(V-c_1,2..n) solve(Vc1,2..n)
否则,问题转化成 s o l v e ( V , 2.. n ) solve(V,2..n) solve(V,2..n).
所以,问题的解决方案就是按照编号从n~1,转变式1。
完全背包,多重背包也是一样的解法。

小结变式1和2

虽然这里只给出了01背包的具体例子,但是其他背包的变式1、2的类似解法也是很好实现的。
抓住重点即可。

①弄清楚现在要解决什么问题,即 s o l v e ( 状 态 ) solve(状态) solve()
②需要求出每一个状态 f [ i ] [ v ] f[i][v] f[i][v]究竟从何处转移过来。

变式3

与计数式DP结合。
还是那句老话,需要求出每一个状态 f [ i ] [ v ] f[i][v] f[i][v]究竟从何处转移过来。
只要明确了这一点,那么转移的时候,判断一下就好了。

变式4

求第k优解。
时间复杂度: O ( V N K ) O(VNK) O(VNK)
我目前解决的一个求第k优解的问题,就是求第K短路。这道题目用A*解决。
最关键的部分在哪里?
选完最优的,剩下的最优解即为第2优的,
以此类推下去,做k次就能够得到第k优解。
核心思想:合并方程时同时维护好前k优的解。

就以多重背包为例,(由于太弱了,所以只会 O ( V ∗ ∑ l o g ( m [ i ] ) ∗ K ) O(V*\sum log(m[i])*K) O(Vlog(m[i])K)的解法)
对于下面的式子,请注意:物品有 ∑ l o g ( m [ i ] ) \sum log(m[i]) log(m[i])个,而不是 n n n个。
多重背包的DP方程: f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − c [ i ] ] + w [ i ] ) f[i][j]=max(f[i-1][j],f[i-1][j-c[i]]+w[i]) f[i][j]=max(f[i1][j],f[i1][jc[i]]+w[i])
f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示做到第i个物品(加了二进制优化之后),第k优的值。
相当于现在有2k个候选值,选出k个来。
这2k个候选值就是: f [ i − 1 ] [ j ] [ 1... k ] , g [ i − 1 ] [ j − c [ i ] ] [ 1.. k ] f[i-1][j][1...k],g[i-1][j-c[i]][1..k] f[i1][j][1...k],g[i1][jc[i]][1..k] g [ i − 1 ] [ j − c [ i ] ] [ k ] = f [ i − 1 ] [ j − c [ i ] ] [ k ] + w [ i ] g[i-1][j-c[i]][k]=f[i-1][j-c[i]][k]+w[i] g[i1][jc[i]][k]=f[i1][jc[i]][k]+w[i]
选出k个,只需要 O ( k ) O(k) O(k)的复杂度。
一般人不会这么无聊,所以k一般不会很大。
毒瘤题:求严格第k优的解的个数…
谁会啊?

总体小结

①背包问题的内容很多。由背包问题引申出来的DP问题数不胜数。
②通过学习背包问题,更应该受到其中的一些思想、各种各样的解法的启发,从而运用到日常的DP练习。
③需要整理思想、各种各样的解法的巧妙原理,将他们联系起来。

你可能感兴趣的:(背包问题)