背包问题学习笔记Part1

背包问题

前言:背包问题的学习笔记和模板例题整理。

01背包

N件物品和一个容量为V的背包。第i件物品的费用是ci,价值是wi。求解将哪些物品装入背包可使价值总和最大。

分析:

  • 每种物品都只有一件。
  • 都只有放或是不放两种选择。
  • fi,v表示只放入前i个物品时在所用体积为v时所能得到的最大价值。
  • 由此可以得出状态转移方程式:fi,v=max(fi1,v,fi1,vci+wi)

解释:

  • 这是一个背包问题的原始状态转移方程式,有很多背包问题的变体都是从此推出而得
  • 将前i个物品放入背包这一过程只会与前i1个物品有关,也就是相当于只考虑i件物品是否放入的问题
  • 如果不放入第i件物品,那么问题转化为i1件物品放入容量为v的背包中,表示出来就是此时:fi,v=fi1,v
  • 如果放入第i件物品,那么问题转化为i1件物品放入容量为vci的背包中,表示出来就是此时:fi,v=fi1,vci+wi
  • 因为要取其中的最优策略,所以要取两种情况中的较大值

空间复杂度优化:

  • 01背包的空间复杂度其实可以优化到O(N)级别
  • 上面讲的思路如果要实现,首先要有一层1n的循环,控制每一次循环得出某个i时的所有fi,v的值
  • 是否能只用一个一维数组f0v就保证在第i次循环推fi,v的值时得到的fv就是之前的fi1,vfi1,vci+wi的值呢?
  • 其实在循环时用v1的顺序推出fv,这样就能保证得到的fv就是fi1,vfi1,vci+wi中的较大值

核心部分代码:

for(int i=1;i<=n;i++)
    for(int j=m;j>=1;j--)
        f[j]=max(f[j-c[i]]+w[i],f[j]);

一些细节:

  • 其实可以对上面的代码进行一个常数优化,因为如果循环中的j比当前的ci要小,便没有继续推的必要,可以把for(int j=m;j>=1;j--)改为for(int j=m;j>=c[i];j--)
  • 如果要求的是恰好装满背包情况下的最大价值,那么除了f0赋值为0,除此之外的f1m都要赋为极小值
  • 如果要求的是不必装满背包情况下的最大价值(比如下面那道模板题),那么整个f0m都赋值为0
  • 分成这两种情况的原因是:初始化数组f时应该给其赋值在没有任何物品放入时的合法状态
  • 如果要求恰好装满,那么没有物品情况下的唯一合法情况就是容量为0的情况被“一无所有”装满时的价值0,其他容量的背包均无合法的解,处于未定义状态,所以赋值为
  • 如果不要求恰好装满,那么此时每个容量下的背包都有合法情况即不装任何物品,所以f0m都赋值为0
  • 关于初始化的问题可以推广到其他种类的背包问题

模板题:

P1048 采药
code

完全背包

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

分析:

  • 这个问题和01背包问题唯一的差别是这个问题里的物品可以取无数件
  • 所以取物品的策略从取和不取两种决策变成取0mci
  • 此时如果按照01背包的思想,令fi,v为前i个物品放入体积为v的背包中的最大价值,依然可以推出这种思路下的状态转移方程式:
  • fi,v=max(fi1,vkci+kwi0cikv)
  • 这样做和01背包一样,有O(NV)种情况,但是每种状况的讨论时间复杂度不在是常数。求出状态fi,v所需的时间复杂度是O(Vci),总时间复杂度变为O(VVci)

优化:

  • 完全背包问题有一个很简单有效的优化,是这样的:若两件物品ij满足ci=wj,则将物品j去掉,不用考虑。这个优化的正确性显然:任何情况下都可将价值小费用高得j换成物美价廉的i,得到至少不会更差的方案。对于随机生成的数据,这个方法往往会大大减少物品的件数,从而加快速度。然而这个并不能改善最坏情况的复杂度,因为有可能特别设计的数据可以一件物品也去不掉。
  • 这个优化可以简单的O(N2)地实现,一般都可以承受。另外,针对背包问题而言,比较不错的一种方法是:首先将费用大于V的物品去掉,然后使用类似计数排序的做法,计算出费用相同的物品中价值最高的是哪个,可以O(V+N)地完成这个优化。

转化:

  • 考虑把完全背包问题转化为01背包问题来解。最简单的想法是,考虑到第i种物品最多选Vci件,于是可以把第i种物品转化为Vci件费用及价值均不变的物品,然后求解这个01背包问题。这样完全没有改进基本思路的时间复杂度,但这毕竟给了我们将完全背包问题转化为01背包问题的思路:将一种物品拆成多件物品。
  • 更高效的转化方法是:把第i种物品拆成费用为ci2k、价值为wi2k的若干件物品,其中k满足ci2kV。这是二进制的思想,因为不管最优策略选几件第i种物品,总可以表示成若干个2k件物品的和。这样把每种物品拆成logVci件物品,是一个很大的改进。

O(VN)的算法:

先放上代码:

for(int i=1;i<=n;i++)
    for(int j=c[i];j<=m;j++)
        f[j]=max(f[j],f[j-c[i]]+w[i]);

可以看出,上面的代码用的是一维数组,与01背包相比,只有第二重循环的循环次序有所不同。
为什么这样改就行?

  • 01背包按照m0的顺序循环,是为了保证当前状态fi,v是由fi1,vci推得,即保证每件物品只选择一次
  • 而完全背包的特点就是物品有无数件,所以在决定状态fi,v时,要考虑到状态fi,vci
  • 或者将基本思路中的方程式带入原方程,可以得到:fi,v=max(fi,v,fi,vci+wi)

模板题:

P1616 疯狂的采药
code

多重背包

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

解法

其实多重背包与完全背包很相似,基本的状态转移方程也只是与之略有不同。
对于第 i 件物品,有 ni+1 种策略:取 0ni 件。
还是令 fi,v 为考虑前 i 个物品恰好放入 v 的空间时的最大价值。
此时的状态转移方程就是: fi,v=max(fi1,vk×ci+k×wi0kni)
时间复杂度是: O(V×ni)
多重背包也能二进制优化。

O(VN) 的解法

单调队列优化DP,本人不会,咕着,以后再补

甚至没有完全的模板题,不过多重背包应该还算好理解,毕竟有前面的铺垫了。

后记

背包问题学习笔记Part1就只写前三种最为经典的背包问题吧。
后面几种不是特别经典的留到后面。
在此之前要写另一篇博客,记录前三种背包问题的几道不是很难的变式题目,毕竟只会个模板完全不够。

完结撒花

posted @   AIskeleton  阅读(45)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示