背包问题(Knapsack problem)是一种组合优化的NP完全问题。它是在1978年由Merkel和Hellman提出的。
问题的描述为:
有一个背包,最多放M kg的物体(物体大小不限);
有n个物体,每个物体的重量为Wi,每个物体完全放入背包后可获得收益Vi。问: 如何放置能获得最大的收益?
背包问题一般分为两种,若每个物体不可拆分,则称为0-1背包问题(和其引申问题),这种问题无法用贪心法求的最优解,应该用动态规划的思想。而若每个物体可以切分,则称为一般背包问题,可以使用贪心法求的最优解。
对于一般背包问题,其思想就是贪心。
贪心策略:先将每一件物品权值与重量的比值按从大到小的顺序排序,然后依次放入背包,也就是性价比最高的先放入背包。
有N件物品和一个容量为V的背包。第i件物品的费用是w[i],价值是v[i]。求解将哪些物品装入背包可使价值总和最大。
这是最常见的背包问题,其特点是:每种物品仅有一件,可以选择放或不放。
用子问题定义状态:即a[i][j]表示前i件物品恰放入一个容量为j的背包可以获得的最大价值。
当不放的时候则是:a[i][j] = a[i-1][j];
放的时候则是:a[i][j] = a[i-1][j-w[i]]+v[i];
如果不放,就保持上一种状态,如果放,就在牺牲容积的情况下增加价值。由于我们求的是最大价值,则其状态转移方程便是:
a[i][j]=max{ a[i-1][j], a[i-1][j-w[i]]+v[i] }。
for(int i=1;i<=n;i++)
for(int j=V;j>=1;j--)
{
if(j>=w[i])
a[i][j]=max(a[i-1][j],a[i-1][j-w[i]]+v[i]);
else
a[i][j]=a[i-1][j];
}
以上方法的时间和空间复杂度均为O(N*V),其中时间复杂度基本已经不能再优化了,但空间复杂度却可以优化到O(V)。
先考虑上面讲的基本思路如何实现,肯定是有一个主循环i=1…N,每次算出来二维数组f[i][0…V]的所有值。那么,如果只用一个数组f [0…V],能不能保证第i次循环结束后f[j]中表示的就是我们定义的状态f[i][j]呢?
f[i][j]是由f[i-1][j]和f [i-1][j-w[i]]两个子问题递推而来,能否保证在推导f[v]时(也即在第i次主循环中推f[j]时)能够得到f[j]和f[j -w[i]]的值呢?事实上,这要求在每次主循环中我们以v=V…0的顺序推f[j],这样才能保证推f[j]时f[j-w[i]]保存的是状态f[i-1][j-c[i]]的值。
代码如下:
for(int i=1;i<=n;i++)
for(int j=V;j>=w[i];j--)
{
f[j]=max(f[j],f[j-w[i]]+v[i]);
}
有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
这个问题非常类似于01背包问题,所不同的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取0件、取1件、取2件……等很多种。如果仍然按照解01背包时的思路,令f[i][j]表示前i种物品恰放入一个容量为j的背包的最大权值。最终得出结论,完全背包和0-1背包的状态方程是基本一致的,但是为什么完全背包的循环是正序[w[i],V],而0-1背包的循环是倒序[V,w[i]],
这是因为完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果f[i][j-w[i]],正序则正好满足其条件。
for(int i=1;i<=n;i++)
for(int j=1;j<=V;j++)
{
if(j>=w[i])
a[i][j]=max(a[i-1][j],a[i-1][j-w[i]]+v[i]);
else
a[i][j]=a[i-1][j];
}
一维优化同0-1背包:
for(int i=1;i<=n;i++)
for(int j=w[i];j<=V;j++)
{
f[j]=max(f[j],f[j-w[i]]+v[i]);
}
有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
与完全背包一样,多重背包也是转化为0-1背包问题来做:把第i种物品换成n[i]件01背包中的物品,则得到了物品数为Σn[i]的01背包问题,直接求解,时间复杂度为O(V*Σn[i])。
for(int i=1;i<=n;i++)
for(int j=V;j>=w[i];j--)
for(int k=1;k<=n[i]&&j-k*w[i]>=0;k++)
f[j]=max(f[j],f[j-w[i]*k]+v[i]*k);
在多重背包的问题中,很多时候Σn[i]会非常大,很有可能超时。
因为1、2、4、8 、16 、 32……2 n 可以组成从1到2(n+1)-1中的任何数
用二进制的思想,我们将第i种物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数。使这些系数分别为1,2,4,…,2(k-1),n[i]-2 k +1,且k是满足n[i]-2k+1>0的最大整数。例如,如果n[i]为13,就将这种物品分成系数分别为1,2,4,6的四件物品。
这样就把原问题转化为了复杂度为O(V*Σlog n[i])的01背包问题。
for(int i=1;i<=n;i++)
{
for(j=1;j<=a[i].num;j<<=1)//分解为2^x
{
v[tot]=j*a[i].v;
w[tot]=j*a[i].w;
tot++;
a[i].num-=j;
}
if(a[i].num>0)//最后一项补齐
{
v[tot]=a[i].num*a[i].v;
w[tot]=a[i].num*a[i].w;
tot++;
}
}
如果将0-1背包、完全背包、多重背包混合起来。也就是说,有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。应该怎么求解呢?
设p[i]=0,1,n(n!=0,1)分别为0-1背包、完全背包、多重背包。
代码如下:
#include
#include
#define maxn 10005
using namespace std;
int t[maxn],c[maxn],p[maxn];
int main()
{
int f[maxn];
int ts1,ts2,te1,te2;
scanf("%d:%d %d:%d",&ts1,&ts2,&te1,&te2);
int time=(te1-ts1)*60+te2-ts2,n;
cin>>n;
for(int i=1;i<=n;i++)
cin>>t[i]>>c[i]>>p[i];
for(int i=1;i<=n;i++)
{
if(p[i]==0)//完全背包
{
for(int j=V;j>=w[i];j--)
f[j]=max(f[j],f[j-w[i]]+v[i]);
}
else if(p[i]==1)//01背包
{
for(int j=w[i];j<=V;j++)
f[j]=max(f[j],f[j-w[i]]+v[i]);
}
else//多重背包
{
for(int j=V;j>=w[i];j--)
for(int k=1;k<=p[i]&&j-k*w[i]>=0;k++)
f[j]=max(f[j],f[j-w[i]*k]+v[i]*k);
}
}
cout<<f[V];
return 0;
}