您的位置:首页 > 其它

对于dd大牛《背包九讲》的总结——第二讲完全背包以及其优化

2019-03-07 14:13 1871 查看

背包哪九讲?
1.01背包问题——每件物品选或者不选
2.完全背包问题——每件物品可以选无限次,爱选多少次选多少次,只要背包容量够用
3.多重背包问题——每个物品选的次数上限不同且有限制
4.混合背包问题——物品很多种,每种物品的信息不同
5.二维费用背包问题——普通的背包问题可能只有重量限制,而二维可能是重量+体积限制
6.分组背包问题——各种问题分成若干组,每组只能选一件,组内物品相互排斥
7.背包问题求方案数
8.求背包问题方案
9.有依赖的背包问题

第二讲:完全背包问题
题目:有N种物品和一个容量为V的背包,每种物品都有无限种可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

基本思路:完全背包问题和01背包问题的差别在于,01背包涉及到的策略是取或不取两种,而完全背包问题每种物品是无限的,因此涉及到的策略有取0件、取1件、取2件……等很多种。
如果按照枚举的办法:仍然可以按照每种物品不同的策略写出状态转移方程以及伪代码:
for(i=0;i<N;i++) //N件物品
  for(j=V;j>=c[i];j++) //背包的容量要大于一件物品的费用c[i]
    for(k=0;k×c[i]<=j;k++) //选择取k件物品
      f[j]=max(f[j],f[j-k×c[i]]+k×w[i]);
如果仍然按照01背包时的思路,令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方程:f[i][v]=max(f[i-1][v- k* c[i]]+k* w[i]|0<=k* c[i]<=v)。这和01背包问题一样有O(N*V)个状态需要求解,但求解每个状态的时间则不是常数了,求解状态f[i][v]的时间是O(v/c[i]),总的复杂度是超过O(VN)的。
将01背包问题的基本思路加以改进,得到了这样一个清晰的方法。这说明01背包问题的方程可以推及到其他类型的背包问题。

一个简单有效的优化:若两件物品i、j满足c[i]<=c[j]且w[i]>=w[j],则将物品j去掉,不用考虑。这个优化的正确性显然:任何情况下就可将价值小费用高的j换成价值大费用小的i,得到至少不会更差的方案。对于随机生成的数据,这个方法往往会大大减少物品的件数,从而加速,但是这个并不能改善最坏情况的复杂度,因为有可能特别设计的数据可以一件物品也去不掉。

转化为01背包问题求解:因为上面提到01背包问题的方程可以推及到其他类型的背包问题,那么我们就可以考虑将完全背包问题转化为01背包问题来解决。最简单的想法是,考虑带第i种物品最多选V/c[i]件,于是可以把第i件物品转化为V/c[i]件费用以及价值均不变的物品,然后求解这个01背包问题。这样完全没有改进基本思路的时间复杂度,但这毕竟给了我们将完全背包问题转化为01背包问题的思路:将一种物品拆成多件物品。

更高效的转化方法是:把第i种物品拆成费用为c[i]*2k、价值为w[i]*2k的若干件物品,其中k满足c[i]*2k<V。这是二进制的思想,因为不管最优策略选几件第i种物品,总可以表示成若干个2k件物品的总和。这样把每种物品拆成O(long(V/c[i]))件物品,是一个很大的改进。但是我们有更优的O(VN)的算法,这个算法使用一维数组,伪代码如下:
for i=1…N
  for v=0…V
    f[v]=max(f[v],f[v-c[i]]+w[i]]);
很容易发现,这个伪代码只和01背包的伪代码的c的循环次序不同。为什么这么改就可以了呢?我们先从01背包为什么要按照v=V…0的逆序来循环来考虑,因为要保证第i次循环中的状态f[i][v]是由状态f[i-1][v-c[i]]递推而来的。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果f[i-1][v-c[i]]。而现在完全背包问题的特点恰是每件物品可选无限件,所以在考虑“加选一件第i件物品”这种策略时,缺正需要一个可能已经选入第i种物品的子结果f[i][v-c[i]],所以就可以并且必须采用v=0…V的顺序循环。这就是这个简单程序为何成立的道理。
这个算法也可以以另外的思路得出。例如,基本思路中的状态转移方程可以等价地变形成这种形式:f[i][v]=max{f[i-1][v],f[i][v-c[i]]+w[i]},将这个方程用一维数组实现,便得到了上面的伪代码。

#include<iostream>
#include<algorithm>
using namespace std;
const int MAX=1010;
int N,V;
int f[MAX];
int c[MAX],w[MAX]; //费用和价值
int main()
{
cin>>N>>V;
for(int i=1;i<=N;i++)
cin>>c[i]>>w[i];
for(int i=1;i<=N;i++)
for(int v=c[i];v<=V;v++)
f[v]=max(f[v],f[v-c[i]]+w[i]);//若v<c[i]时,f[v]=f[v]没必要写出来
cout<<f[V]<<endl;//为什么直接输出f[v]不用找最大的,看第一讲的初始化说明
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: