在这之前,我们讲到了01背包与完全背包。这次,我们来讲一讲很搞笑又稀奇古怪的(我不认为)多重背包问题。
多重背包问题
经典例题
例【n+2】这位流浪的探险者被困在了海岛上。有一天,他发现了一艘船,于是,他就乘坐这艘船准备回家。这时,他在岛上获取了N种东西,每件物品有n件真古怪哈,价值c,费用w,他需要让带回去的东西有更大的价值,所以,编程帮助他,找出最佳搭配。
感觉大家已经能够猜出这个悲催的人是谁了,就是大名鼎鼎的——鲁滨孙大人!!!
基本思路
这个题目不用说了吧,很简单,和完全背包基本一样。现在,我们只要把完全背包的方程略微改动即可。这种背包问题对于每种物品有n[i]+1(此处n为最多取物品的数量)种策略:取0个,1个,…n[i]个。令f[i][v]表示前i种物品恰放入容量为v的最大价值,则得出动态转移方程:
f
(
i
)
(
v
)
=
m
a
x
(
f
(
i
−
1
)
(
v
)
,
f
(
i
−
1
)
(
v
−
k
∗
w
(
i
)
)
+
k
∗
c
(
i
)
∣
0
<
=
k
<
=
n
(
i
)
)
f(i)(v)=max(f(i-1)(v),f(i-1)(v-k*w(i))+k*c(i)|0<=k<=n(i))
f(i)(v)=max(f(i−1)(v),f(i−1)(v−k∗w(i))+k∗c(i)∣0<=k<=n(i))此种算法时间复杂度为
O
(
V
∗
∑
n
(
i
)
)
O(V*∑n(i))
O(V∗∑n(i))。
结论得出的伪代码如下:
for(i=1...n)
for(v=V...0) //此地根据01写出,由于与完全不同,所以是V...0
for(k=0...n[i])
{
if(v-k*v[i]<0) break; //在for之前无法判断,只好在这里
f[i][v]=max(f[i-1][v],f[i-1][v-k*w[i]]+k*c[i]);
}
换一种好想好写的分析方法,将多重背包转换为01背包问题:把第i种物品换成n[i]件01背包中的物品,则得到了物品数为 ( ∑ n ( i ) ) (∑n(i)) (∑n(i))的01背包问题,直接求解,时间复杂度依然是 ( O ( V ∗ ∑ n ( i ) ) ) (O(V*∑n(i))) (O(V∗∑n(i)))。
但是,我们要做一个懒人 勤快之人,期望将他转换成01背包问题之后就像上次完全背包问题一样降低复杂度。仍然考虑二进制的思想,把第i件物品换成若干件物品,使原问题中第i中物品可以取用的每种策略(取0~n[i]件物品)是与若干件物品代换后的相同。注意,取超过n[i]件物品的策略肯定不能出现。
于是,方法出现。将第i种物品换成n[i]件相同的物品,其中每件物品都有一个系数,这件物品的费用和价值均是原来的费用与价值乘上这个系数,使这些系数分别为1,2,4,···, 2 k − 1 2^{k-1} 2k−1, n ( i ) − 2 k + 1 n(i)-2^k+1 n(i)−2k+1,且k是满足 n ( i ) − 2 k + 1 > 0 n(i)-2^k+1>0 n(i)−2k+1>0的最大整数(这里请注意,分出的数是可以组合出n[i]内所有数字)。
是不是感觉蒙了!炸了!
好,我们来举一个例子就可以明白了。总结上面的方法,假设n[i]=13,就将这种物品分成系数分别为1,2,4,6的四件物品(分成了四件一样但意义不同的物品,如果试试看,你就会发现,1,2,4,6可以组成13以内的任何数字。1,2,1+2,4,1+4,6,1+6,2+6,1+2+6,4+6,1+4+6,2+4+6,1+2+4+6=13)
对于上面有趣的式子,可以分0… 2 k − 1 2^{k-1} 2k−1和 2 k 2^k 2k…2n[i]讨论得出,希望读者自己思考思考,开动脑筋。
分成的这些物品系数的和为n[i],表示不可能取多于n[i]的第i件物品。这样就将第i件物品分为了 l o g ∗ n ( i ) log*n(i) log∗n(i)种物品,将原问题的复杂度转化为 O ( V ∗ ∑ l o g n ( i ) ) O(V*∑logn(i)) O(V∗∑logn(i))这是很大的改进呀!
分析结束,此处贴出慢版的程序:
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int v[10000],w[10000],s[10000],f[10000],n,m,i,j,k;
int main ()
{
cin>>n>>m;
for (i=1; i<=n; i++)
cin>>v[i]>>w[i]>>s[i];
for (i=1; i<=n; i++)
for (j=m; j>=0; j--)
for (k=1; k<=s[i]; k++)
{
if (j-k*v[i]<0) break;
f[j]=max(f[j-k*v[i]]+w[i]*k,f[j]);
}
cout<<f[m];
}
如有需要快版程序,请下载附带的资源包或发私信给我。
写文章不易,请大家多多支持,谢谢。