01背包和完全背包
刚接触背包问题 看了很多博客 才算弄懂这01背包和完全背包 所以赶紧发个博客记录下来 虽然我标注的是原创 但是肯定会引用到别人的东西 如果是引用的 我会注明出处 尊重原创嘛
先说下
01背包
01背包的问题描述是这样的:
有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。
01背包的特点就是:每种物品都只有一件,选择放还是不放。
状态转移方程是:
f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}
对于这方方程其实并不难理解,方程之中,现在需要放置的是第i件物品,这件物品的体积是c[i],价值是w[i],因此f[i-1][v]代表的就是不将这件物品放入背包,而f[i-1][v-c[i]]+w[i]则是代表将第i件放入背包之后的总价值,比较两者的价值,得出最大的价值存入现在的背包之中。
用代码实现:
- for(i = 1; i<=n; i++)
- {
- for(j = v; j>=c[i]; j--)//在这里,背包放入物品后,容量不断的减少,直到再也放不进了
- {
- f[i][v]=max(f[i-1][v],f[i-1][v-c[i]]+w[i]);
- }
- }
HDU2546:饭卡
http://acm.hdu.edu.cn/showproblem.php?pid=2546
很经典的一道01背包题,要注意的是这里只要剩余的钱不低于5元,就可以购买任何一件物品,所以5在这道题中是很特许的,再使用01背包之前,我们首先要在现在所拥有的余额中保留5元,用这五元去购买最贵的物品,而剩下的钱就是背包的总容量,可以随意使用,因此可得代码
(出自: http://blog.csdn.net/libin56842/article/details/9338841)
完全背包:
完全背包是在N 种 物品中选取若干件(同一种物品可多次选取)放在空间为V的背包里,每 种 物品的体积为C[i] ,与之相对应的价值为W[i] .求解怎么装物品可使背包里物品总价值最大。
阶段是:在前N件物品中,选取若干件物品放入背包中;
状态是:在前N件物品中,选取若干件物品放入所剩空间为W的背包中的所能获得的最大价值;
决策是:第N件物品放或者不放;
状态转移方程是:
f[i][v]=max(f[i-1][v-k*c[i]]+k*w[i]) 0<=k*c[i]<=v
换成更易理解的方式 则方程转换为:
for(i=0;i<n;i++)
{
for(v=0;v<V;v++)
f[i][v]=max(f[i-1][v] , f[i-1][v-c[i]]+w[i])
}
更高效的转化方法是:
因为同种物品可以多次选取,那么第i种物品最多可以选取V/C[i]件价值不变的物品,然后就转化为01背包问题。整个过程的时间复杂度并未减少。如果把第i种物品拆成体积为C[i]×2k价值W[i]×2k的物品,其中满足C[i]×2k≤V。那么在求状态F[i][j]时复杂度就变为O(log2(V/C[i]))。整个时间复杂度就变为O(NVlog2(V/C[i]))
但是还有更优的算法:
将f[i][v]数组转化成一维数组:
for(i=0;i<m;i++)
{
for(j=c[i];j<=n;j++)
f[j]=max(f[j],f[j-c[i]]+w[i]);
}
{
for(j=c[i];j<=n;j++)
f[j]=max(f[j],f[j-c[i]]+w[i]);
}
想必大家看出了和01背包的区别,这里的内循环是顺序的,而01背包是逆序的。
现在关键的是考虑:为何完全背包可以这么写?
在次我们先来回忆下,01背包逆序的原因?是为了是max中的两项是前一状态值,这就对了。
那么这里,我们顺序写,这里的max中的两项当然就是当前状态的值了,为何?
因为每种背包都是 无限 的。当我们把i从1到N循环时,f[v]表示容量为v在前i种背包时所得的价值,这里我们要添加的不是前一个背包,而是当前背包。所以我们要考虑的当然是当前状态
现在关键的是考虑:为何完全背包可以这么写?
在次我们先来回忆下,01背包逆序的原因?是为了是max中的两项是前一状态值,这就对了。
那么这里,我们顺序写,这里的max中的两项当然就是当前状态的值了,为何?
因为每种背包都是 无限 的。当我们把i从1到N循环时,f[v]表示容量为v在前i种背包时所得的价值,这里我们要添加的不是前一个背包,而是当前背包。所以我们要考虑的当然是当前状态
然后我们看一道题:
直接说题意,完全背包定义有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的体积是c,价值是w。求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。本题要求是背包恰好装满背包时,求出最大价值总和是多少。如果不能恰好装满背包,输出NO
Input
第一行: N 表示有多少组测试数据(N<7)。
接下来每组测试数据的第一行有两个整数M,V。 M表示物品种类的数目,V表示背包的总容量。(0<M<=2000,0<V<=50000)
接下来的M行每行有两个整数c,w分别表示每种物品的重量和价值(0<c<100000,0<w<100000)
接下来每组测试数据的第一行有两个整数M,V。 M表示物品种类的数目,V表示背包的总容量。(0<M<=2000,0<V<=50000)
接下来的M行每行有两个整数c,w分别表示每种物品的重量和价值(0<c<100000,0<w<100000)
Output
对应每组测试数据输出结果(如果能恰好装满背包,输出装满背包时背包内物品的最大价值总和。 如果不能恰好装满背包,输出NO)
Sample Input
2 1 5 2 2 2 5 2 2 5 1
Sample Output
NO 1
(出自:http://blog.csdn.net/red_flame/article/details/8152436)
这里唯一不同的是背包如果不能完全装满,则输出NO,这里需要一个技巧,就是初始化时f[0],其余的均为-max,只有这样最大值为正时,只能通过f[0]在相加其他价值得到,如 背包体积为4时, 一种物品体积2,价值2; 则 f[0]=0; f[1]=-max; f[2]=max(f[2],f[0]+w[i])=2; 注意若背包不需要全部装满时,f[3]本该为2的,但此时 f[3]=max(f[3],f[1]+2)=max(f[3],2-max)=2-max; 负无穷
#include<iostream>
#include<stdio.h>
#include<string>
#include<cstring>
using namespace std;
int f[50010],c[2010],w[2010];
int main()
{
int test,m,v,i,j;
scanf("%d",&test);
while(test--)
{
memset(f,-10000000,sizeof(f)); //用来判断背包是否装满
f[0]=0;
scanf("%d%d",&m,&v);
for(i=1;i<=m;++i)
scanf("%d%d",&c[i],&w[i]);
for(i=1;i<=m;++i)
for(j=0;j<=v;++j) //注意此循环与01背包的用一维数组表示的状态方程的区别,一个循环逆序,一个顺序
if(j>=c[i])
f[j]=f[j]>(f[j-c[i]]+w[i])?f[j]:f[j-c[i]]+w[i]; //完全背包的状态方程,可画图加深理解
if(f[v]<0) //背包为装满
printf("NO\n");
else
printf("%d\n",f[v]);
}
}
这里唯一不同的是背包如果不能完全装满,则输出NO,这里需要一个技巧,就是初始化时f[0],其余的均为-max,只有这样最大值为正时,只能通过f[0]在相加其他价值得到,如 背包体积为4时, 一种物品体积2,价值2; 则 f[0]=0; f[1]=-max; f[2]=max(f[2],f[0]+w[i])=2; 注意若背包不需要全部装满时,f[3]本该为2的,但此时 f[3]=max(f[3],f[1]+2)=max(f[3],2-max)=2-max; 负无穷
#include<iostream>
#include<stdio.h>
#include<string>
#include<cstring>
using namespace std;
int f[50010],c[2010],w[2010];
int main()
{
int test,m,v,i,j;
scanf("%d",&test);
while(test--)
{
memset(f,-10000000,sizeof(f)); //用来判断背包是否装满
f[0]=0;
scanf("%d%d",&m,&v);
for(i=1;i<=m;++i)
scanf("%d%d",&c[i],&w[i]);
for(i=1;i<=m;++i)
for(j=0;j<=v;++j) //注意此循环与01背包的用一维数组表示的状态方程的区别,一个循环逆序,一个顺序
if(j>=c[i])
f[j]=f[j]>(f[j-c[i]]+w[i])?f[j]:f[j-c[i]]+w[i]; //完全背包的状态方程,可画图加深理解
if(f[v]<0) //背包为装满
printf("NO\n");
else
printf("%d\n",f[v]);
}
}