【01背包】
1.问题描述:
有 N 件物品和一个容量为 V 的背包。放入第 i 件物品耗费的费用是 Ci,得到的价值是Wi。求解将哪些物品装入背包可使价值总和最大。
这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。
用子问题定义状态:即 F[i,v] 表示前 i 件物品恰放入一个容量为 v 的背包可以获得的最大价值。则其状态转移方程便是:
F[i,v]= max{F[i− 1,v],F[i− 1,v− Ci]+ Wi}
这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生出来的。所以有必要将它详细解释一下:“将前 i 件物品放入容量为 v的背包中”这个子问题,若只考虑第i件物品的策略(放或不放),那么就可以转化为一个只和前i − 1 件物品相关的问题。如果不放第i 件物品,那么问题就转化为“前i − 1 件物品放入容量为v 的背包中”,价值为 F[i− 1,v];如果放第i件物品,那么问题就转化为“前 i − 1 件物品放入剩下的容量为v− Ci 的背包中”,此时能获得的最大价值就是F[i − 1,v−Ci] 再加上通过放入第 i 件物品获得的价值Wi。
2.初始化:
有的题目要求“恰好装满背包”时的最优解,有的题目则并没有要求必须把背包装满。
一种区别这两种问法的实现方法是在初始化的时候有所不同。
如果是第一种问法,要求恰好装满背包,那么在初始化时
除了 F[0] 为 0,其它F[1..V ] 均设为 -∞,这样就可以保证最终得到的 F[V ] 是一种恰好装满背包的最优解。
如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将 F[0..V ]全部设为 0。
3.公式一般形式:
1)非状态压缩方程:
F[0,0..V] ← 0
for i ←1 to N for v ← Ci toV
F[i,v] ← max{F[i ? 1,v],F[i? 1,v? Ci] + Wi}
2)进行空间优化的状态压缩方程
F[0..V ]←0
for i← 1 to N forv ← V to Ci
F[v] ← max{F[v],F[v? Ci]+ Wi}
第二层循环逆序的原因:
注意一点,我们是由第 i - 1 次循环的两个状态推出 第 i 个状态的,而且 v > v - weight[i],则对于第i次循环,背包容量只有当V..0循环时,才会先处理背包容量为v的状况,后处理背包容量为 v-weight[i] 的情况。
具体来说,由于,在执行v时,还没执行到v - weight[i]的,因此,f[v - weight[i]]保存的还是第i - 1次循环的结果。即在执行第i次循环 且 背包容量为v时,此时的f[v]存储的是 f[i - 1][v] ,此时f[v-weight[i]]存储的是f[i - 1][v-weight[i]]。
相反,如果在执行第 i 次循环时,背包容量按照0..V的顺序遍历一遍,来检测第 i 件物品是否能放。此时在执行第i次循环 且 背包容量为v时,此时的f[v]存储的是 f[i - 1][v] ,但是,此时f[v-weight[i]]存储的是f[i][v-weight[i]]。
因为,v > v - weight[i],第i次循环中,执行背包容量为v时,容量为v - weight[i]的背包已经计算过,即f[v - weight[i]]中存储的是f[i][v - weight[i]]。即,对于01背包,按照增序枚举背包容量是不对的。
4.实际题目求解:
hdu 2602 bone collector(01背包)
http://acm.hdu.edu.cn/showproblem.php?pid=2602
分析:标准的不必装满的01,背包....裸裸的贴上去代码~
Bone Collector
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 48124 Accepted Submission(s): 20074
The bone collector had a big bag with a volume of V ,and along his trip of collecting there are a lot of bones , obviously , different bone has different value and different volume, now given the each bone’s value along his trip , can you calculate out the maximum of the total value the bone collector can get ?
![](https://i-blog.csdnimg.cn/blog_migrate/0bf307b7a4c44236bd26a289041b24d5.jpeg)
Followed by T cases , each case three lines , the first line contain two integer N , V, (N <= 1000 , V <= 1000 )representing the number of bones and the volume of his bag. And the second line contain N integers representing the value of each bone. The third line contain N integers representing the volume of each bone.
1 5 10 1 2 3 4 5 5 4 3 2 1
14
hdu 2546:饭卡(01背包)
http://acm.hdu.edu.cn/showproblem.php?pid=2546
饭卡
Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 21034 Accepted Submission(s): 7336
某天,食堂中有n种菜出售,每种菜可购买一次。已知每种菜的价格以及卡上的余额,问最少可使卡上的余额为多少。
第一行为正整数n,表示菜的数量。n<=1000。
第二行包括n个正整数,表示每种菜的价格。价格不超过50。
第三行包括一个正整数m,表示卡上的余额。m<=1000。
n=0表示数据结束。
1 50 5 10 1 2 3 2 1 1 2 3 2 1 50 0
-45 32
/*
余额里的钱如果小于5元直接输出
如果大于5元,取出5元用来买最贵的菜,
剩余钱就是
利用0,1背包求最优解
*/
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int dp[1003];
int w[1003];
int main()
{
int V,n;
while(cin>>n,n)
{
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++)
{
cin>>w[i];
}
cin>>V;
sort(w+1,w+1+n);
if(V<5)
{
cout<<V<<endl;
continue;
}
V-=5;
//最后剩余的5元用来吗买最贵的菜
//状态压缩的写法;
for(int i=1;i<n;i++)
for(int v=V;v>=w[i];v--)
{
dp[v]=max(dp[v],dp[v-w[i]]+w[i]);
}
cout<<V+5-w[n]-dp[V]<<endl;
}
return 0;
}
【完全背包】
1.问题描述:
这个问题非常类似于 01 背包问题,所不同的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取0 件、取 1 件、取 2 件……直至取 ⌊V/Ci⌋件等许多种。
2.初始化:
有的题目要求“恰好装满背包”时的最优解,有的题目则并没有要求必须把背包装满。
一种区别这两种问法的实现方法是在初始化的时候有所不同。
如果是第一种问法,要求恰好装满背包,那么在初始化时
除了 F[0] 为 0,其它F[1..V ] 均设为 -∞,这样就可以保证最终得到的 F[V ] 是一种恰好装满背包的最优解。
如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将 F[0..V ]全部设为 0。
3.公式一般形式:
F[0..V ]←0
for i ← 1 to N
for v ← Ci to V
F[v] ← max(F[v],F[v−Ci]+ Wi)
这里只有第二层for循环和01背包不同,why?
首先想想为什么 01 背包中要按照v递减的次序来循环。
让 v 递减是为了保证第i次循环中的状态F[i;v]是由状态F[i-1;v-Ci]递推而来。
换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第 i 件物品”这件策
略时,依据的是一个绝无已经选入第 i 件物品的子结果F[i-1;v-Ci]。而现在完全背
包的特点恰是每种物品可选无限件,所以在考虑“加选一件第 i 种物品”这种策略时,
却正需要一个可能已选入第 i 种物品的子结果F[i;v-Ci],所以就可以并且必须采用v
递增的顺序循环。这就是这个简单的程序为何成立的道理。
4.实际题目求解
但是对于女生来说,卡路里(热量)是天敌啊!
资深美女湫湫深谙“胖来如山倒,胖去如抽丝”的道理,所以她希望你能帮忙制定一个食谱,能使她吃得开心的同时,不会制造太多的天敌。
当然,为了方便你制作食谱,湫湫给了你每日食物清单,上面描述了当天她想吃的每种食物能带给她的幸福程度,以及会增加的卡路里量。
每组数据以一个整数n开始,表示每天的食物清单有n种食物。
接下来n行,每行两个整数a和b,其中a表示这种食物可以带给湫湫的幸福值(数值越大,越幸福),b表示湫湫吃这种食物会吸收的卡路里量。
最后是一个整数m,表示湫湫一天吸收的卡路里不能超过m。
[Technical Specification]
1. 1 <= n <= 100
2. 0 <= a,b <= 100000
3. 1 <= m <= 100000
3 3 3 7 7 9 9 10 5 1 1 5 3 10 3 6 8 7 5 6
10 20
不必装满的裸裸的完全背包
#include<iostream>
#include<cstring>
using namespace std;
int c[103];
int w[103];
int dp[100005];
int main()
{
int n;
while(cin>>n)
{
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++)
{
cin>>w[i]>>c[i];
}
int V;
cin>>V;
for(int i=1;i<=n;i++)
for(int v=c[i];v<=V;v++)
{
dp[v]=max(dp[v],dp[v-c[i]]+w[i]);
}
cout<<dp[V]<<endl;
}
return 0;
}
3 多重背包问题
3.1
题目有 N 种物品和一个容量为 V 的背包。第 i 种物品最多有 M i 件可用,每件耗费的
空间是 C i ,价值是 W i 。求解将哪些物品装入背包可使这些物品的耗费的空间总和不超
过背包容量,且价值总和最大。
3.2 基本算法
即可。
因为对于第 i 种物品有 M i + 1 种策略:取 0 件,取 1 件……取 M i 件。令 F [ i; v ]
表示前 i 种物品恰放入一个容量为 v 的背包的最大价值,则有状态转移方程:
F[i,v] =maxfF[i-1; v-k ∗Ci] +k∗Wij0≤k≤Mi g
复杂度是 O ( V Σ M i )
3.3 转化为01 背包问题
另一种好想好写的基本方法是转化为 01 背包求解:把第 i 种物品换成 Mi 件 01 背包中的物品,则得到了物品数为ΣMi的 01 背包问题。直接求解之,复杂度仍然是
O(V ΣMi)。
3.4 多重 背包便准代码
def MultiplePack(F,C,W,M)if C · M ≥ V
CompletePack(F,C,W)return k ← 1
while k < M
ZeroOnePack(kC,kW)
ZeroOnePack(C · M,W · M)
3.5 不考虑实际价值值考虑体积的多重 背包标准代码
F[0,1...V] ← −1
F[0,0] ← 0
for i ← 1 to N for j ← 0 to V
if F[i − 1][j] ≥ 0
F[i][j]= Mi else
F[i][j]= −1 for j ←0 to V−Ci
if F[i][j]> 0
F[i][j+ Ci] ← max{F[i][j+Ci],F[i][j] − 1} 最终F[N][0...V] 便是多重背包可行性问题的答案。
悼念512汶川大地震遇难同胞——珍惜现在,感恩生活
Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 25710 Accepted Submission(s): 10869
为了挽救灾区同胞的生命,心系灾区同胞的你准备自己采购一些粮食支援灾区,现在假设你一共有资金n元,而市场有m种大米,每种大米都是袋装产品,其价格不等,并且只能整袋购买。
请问:你用有限的资金最多能采购多少公斤粮食呢?
后记:
人生是一个充满了变数的生命过程,天灾、人祸、病痛是我们生命历程中不可预知的威胁。
月有阴晴圆缺,人有旦夕祸福,未来对于我们而言是一个未知数。那么,我们要做的就应该是珍惜现在,感恩生活——
感谢父母,他们给予我们生命,抚养我们成人;
感谢老师,他们授给我们知识,教我们做人
感谢朋友,他们让我们感受到世界的温暖;
感谢对手,他们令我们不断进取、努力。
同样,我们也要感谢痛苦与艰辛带给我们的财富~
![](https://i-blog.csdnimg.cn/blog_migrate/33ac27bf69fb95d108f1727065c96c20.jpeg)
1 8 2 2 100 4 4 100 2
400
//f[i][v]=max(f[i-1][v],f[i-1][v-kc[i]]+k*w[i]) 0<=k<=m[i]
//m为每件物体数量数组
//c为体积数组
//w为价值数组
//将多重背包看成01背包,时间复杂度稍微较高
#include<iostream>
#include<cstring>
using namespace std;
int V;
int c[101];
int m[101];
int w[101];
int dp[201];
int n;
int main()
{
int t;
cin>>t;
while(t--)
{
memset(dp,0,sizeof(dp));
cin>>V>>n;
for(int i=1;i<=n;i++)
{
cin>>c[i]>>w[i]>>m[i];
}
for(int i=1;i<=n;i++)
for(int k=1;k<=m[i];k++)//特别注意这儿k是从1:m[i]
for(int v=V;v>=c[i];v--)
{
dp[v]=max(dp[v],dp[v-c[i]]+w[i]);
}
cout<<dp[V]<<endl;
}
return 0;
}
多重背包标准代码:
<pre name="code" class="cpp">#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
const int MAXN=110;
int dp[MAXN];
int value[MAXN];//每袋的价格
int weight[MAXN];//每袋的重量
int bag[MAXN];//袋数
int nValue,nKind;
//0-1背包,代价为cost,获得的价值为weight
void ZeroOnePack(int cost,int weight)
{
for(int i=nValue;i>=cost;i--)
dp[i]=max(dp[i],dp[i-cost]+weight);
}
//完全背包,代价为cost,获得的价值为weight
void CompletePack(int cost,int weight)
{
for(int i=cost;i<=nValue;i++)
dp[i]=max(dp[i],dp[i-cost]+weight);
}
//多重背包
void MultiplePack(int cost,int weight,int amount)
{
if(cost*amount>=nValue) CompletePack(cost,weight);
else
{
int k=1;
while(k<amount)
{
ZeroOnePack(k*cost,k*weight);
amount-=k;
k<<=1;
}
ZeroOnePack(amount*cost,amount*weight);//这个不要忘记了,经常掉了
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
//这个dp的初始化一定不要忘记,可以不装满则初始化为0,
//否则dp[0]=0,其余的为-INF
memset(dp,0,sizeof(dp));
scanf("%d%d",&nValue,&nKind);
for(int i=0;i<nKind;i++)
scanf("%d%d%d",&value[i],&weight[i],&bag[i]);
for(int i=0;i<nKind;i++)
MultiplePack(value[i],weight[i],bag[i]);
printf("%d\n",dp[nValue]);
}
return 0;
}
多重部分和问题
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int n,a[110],m[110],k;
int dp[110000];//初始化的值应为没有任何一个数字可以被选中时的值
int main()
{
while(cin>>n)
{
memset(dp,-1,sizeof(dp));
dp[0]=0;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)cin>>m[i];
cin>>k;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=k;j++)
{
if(dp[j]>=0)dp[j]=m[i];
else if(j<a[i]||dp[j-a[i]]<=0)dp[j]=-1;
else dp[j]=dp[j-a[i]]-1;
}
}
if(dp[k]>=0)cout<<"yes"<<endl;
else cout<<"no"<<endl;
}
return 0;
}