01背包问题:用于解决n个不同物品(每个物品只有“一件”),计算在有容量限制的条件下,如何使价值最大
在物品比较少,背包容量比较小时怎么解决?用一个数组f[i][j]表示,在只有i个物品,容量为j的情况下背包问题的最优解,那么当物品种类变大为i+1时,最优解是什么?第i+1个物品可以选择放进背包或者不放进背包(这也就是0和1),假设放进背包(前提是放得下),那么f[i+1][j]=f[i][j-weight[i+1]+value[i+1];如果不放进背包,那么f[i+1][j]=f[i][j]。
这就得出了状态转移方程:f[i+1][j]=max(f[i][j],f[i][j-weight[i+1]+value[i+1])。
例如:
有编号分别为a,b,c,d,e的五件物品,它们的重量分别是2,2,6,5,4,它们的价值分别是6,3,5,4,6,现在给你个承重为10的背包,如何让背包里装入的物品具有最大的价值总和?
name | weight | value | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
a | 2 | 6 | 0 | 6 | 6 | 9 | 9 | 12 | 12 | 15 | 15 | 15 |
b | 2 | 3 | 0 | 3 | 3 | 6 | 6 | 9 | 9 | 9 | 10 | 11 |
c | 6 | 5 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 10 | 11 |
d | 5 | 4 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 10 | 10 |
e | 4 | 6 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
这张表是至底向上,从左到右生成的。
模版:
//hdoj 2602
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int bag[1010],val[1010],vol[1010];
int main()
{
int t,i,j;
scanf("%d",&t);
while(t--)
{
int n,v;
scanf("%d%d",&n,&v);
memset(bag,0,sizeof(bag));
for(i=1;i<=n;i++)
{
scanf("%d",&val[i]);
}
for(i=1;i<=n;i++)
{
scanf("%d",&vol[i]);
}
for(i=1;i<=n;i++)
{
for(j=v;j>=vol[i];j--)
{
bag[j]=max(bag[j],bag[j-vol[i]]+val[i]);
}
}
printf("%d\n",bag[v]);
}
return 0;
}
完全背包问题:用于解决有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
完全背包按其思路仍然可以用一个二维数组来写出:
f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<=v}
转化为一维数组:
这个算法使用一维数组,先看伪代码:
for i=1..N
for v=0..V //这里和0 -1背包的区别
f[v]=max{f[v],f[v-cost]+weight}
例题:完全背包
Piggy-Bank
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 16342 Accepted Submission(s): 8238
But there is a big problem with piggy-banks. It is not possible to determine how much money is inside. So we might break the pig into pieces only to find out that there is not enough money. Clearly, we want to avoid this unpleasant situation. The only possibility is to weigh the piggy-bank and try to guess how many coins are inside. Assume that we are able to determine the weight of the pig exactly and that we know the weights of all coins of a given currency. Then there is some minimum amount of money in the piggy-bank that we can guarantee. Your task is to find out this worst case and determine the minimum amount of cash inside the piggy-bank. We need your help. No more prematurely broken pigs!
3 10 110 2 1 1 30 50 10 110 2 1 1 50 30 1 6 2 10 3 20 4
The minimum amount of money in the piggy-bank is 60. The minimum amount of money in the piggy-bank is 100. This is impossible.
模版:
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f
int main()
{
int t,i,j,e,f,n,p[550],w[550],bag[10010];
scanf("%d",&t);
while(t--)
{
int e,f,n;
memset(bag,INF,sizeof(bag));
scanf("%d%d",&e,&f);
scanf("%d",&n);
for(i=0;i<n;i++)
{
scanf("%d%d",&p[i],&w[i]);
}
bag[0]=0;
for(i=0;i<n;i++)
{
for(j=w[i];j<=f-e;j++)
{
bag[j]=min(bag[j],bag[j-w[i]]+p[i]);
}
}
if(bag[f-e]>=INF)printf("This is impossible.\n");
else printf("The minimum amount of money in the piggy-bank is %d.\n",bag[f-e]);
}
return 0;
}
寒冰王座
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 13258 Accepted Submission(s): 6775
死亡骑士:"我要买道具!"
地精商人:"我们这里有三种道具,血瓶150块一个,魔法药200块一个,无敌药水350块一个."
死亡骑士:"好的,给我一个血瓶."
说完他掏出那张N元的大钞递给地精商人.
地精商人:"我忘了提醒你了,我们这里没有找客人钱的习惯的,多的钱我们都当小费收了的,嘿嘿."
死亡骑士:"......"
死亡骑士想,与其把钱当小费送个他还不如自己多买一点道具,反正以后都要买的,早点买了放在家里也好,但是要尽量少让他赚小费.
现在死亡骑士希望你能帮他计算一下,最少他要给地精商人多少小费.
注意:地精商店只有题中描述的三种道具.
2 900 250
0 50
//ac1248
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int main()
{
int t,a[3]={150,200,350},bag[10010];
scanf("%d",&t);
while(t--)
{
int n,i,j;
scanf("%d",&n);
memset(bag,0,sizeof(bag));
for(i=0;i<3;i++)
{
for(j=a[i];j<=n;j++)
{
bag[j]=max(bag[j],bag[j-a[i]]+a[i]);
}
}
printf("%d\n",n-bag[n]);
}
return 0;
}
f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k<=n[i]}
方法是:将第i种物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数。使这些系数分别为 1,2,4,...,2^(k-1),n[i]-2^k+1,且k是满足n[i]-2^k+1>0的最大整数。例如,如果n[i]为13,就将这种 物品分成系数分别为1,2,4,6的四件物品。
分成的这几件物品的系数和为n[i],表明不可能取多于n[i]件的第i种物品。另外这种方法也能保证对于0..n[i]间的每一个整数,均可以用若干个系数的和表示,这个证明可以分0..2^k-1和2^k..n[i]两段来分别讨论得出,并不难,希望你自己思考尝试一下。
Coins
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 10015 Accepted Submission(s): 3991
You are to write a program which reads n,m,A1,A2,A3...An and C1,C2,C3...Cn corresponding to the number of Tony's coins of value A1,A2,A3...An then calculate how many prices(form 1 to m) Tony can pay use these coins.
3 10 1 2 4 2 1 1 2 5 1 4 2 1 0 0
8 4
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int value[110],num[110],vis[100100],v;
void fun(int value,int num)
{
if(value*num>=v)
{
for(int i=value;i<=v;i++)
vis[i]=max(vis[i],vis[i-value]+value);//在价格和数量超过给定m时,采用完全背包
return ;
}
else{
int k=1;
while(k<num)
{
for(int i=v;i>=value*k;i--)
{
vis[i]=max(vis[i],vis[i-value*k]+value*k);//采用01背包,计算各种情况
}
num-=k;
k<<=1;
}
for(int j=v;j>=value*num;j--)//例如:18分成1,2,4,6,"5",这里是将5的情况计算进去
{
vis[j]=max(vis[j],vis[j-value*num]+value*num);
}
}
}
int main()
{
int i,j,n;
while(scanf("%d%d",&n,&v),n|v)
{
for(i=0;i<n;i++)
{
scanf("%d",&value[i]);
}
for(i=0;i<n;i++)
{
scanf("%d",&num[i]);
}
memset(vis,0,sizeof(vis));
for(i=0;i<n;i++)
{
fun(value[i],num[i]);
}
int k=0;
for(i=1;i<=v;i++)
{
if(vis[i]==i)
k++;
}
printf("%d\n",k);
}
return 0;
}