10.背包问题与滚动数组

背包问题模板

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

01背包

给n个物品和一个容量是m的背包,每个物品有两个属性,体积vi和价值wi,每件物品只能用一次。

//n个物体,容量为m
//体积v[i] 价值w[i]
//状态方程f[i][j]表示前i件物品(不一定全部放入)放入一个容量为j的背包可以获得的最大价值 
for(int i=1;i<=n;i++)
{
	for(int j=0;j<=m;j++)
	{
		f[i][j]=f[i-1][j];
		//初始默认第i个物品没有放入背包 
		if(j>=v[i])		//如果背包容量大于第i件物体的体积 
			f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
			//将f[i][j]更新
	}
}

01背包优化

for(int i=1;i<=n;i++)
{
	for(int j=m;j>=v[i];j--)	//从后往前覆盖 
	{
		f[j]=max(f[j],f[j-v[i]]+w[i]);
	}
}

完全背包

有 n种物品和一个容量是 m 的背包,每种物品都有无限件可用。

for(int i=1;i<=n;i++)
{
	for(int j=0;j<=m;j++)
	{
		for(int k=0;k*v[i]<=j;k++)
			f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+w[i]*k);
	}
}

完全背包优化

for(int i=1;i<=n;i++)
{
	for(int j=v[i];j<=m;j++)	//从前往后遍历
	{
		f[j]=max(f[j],f[j-v[i]]+w[i]);
	}
}

多重背包

有n种物品和一个容量是m 的背包。
第 i种物品最多有 s[i] 件

for(int i=1;i<=n;i++)
{
	for(int j=0;j<=m;j++)
	{
		for(int k=0;k<=s[i]&&k*v[i]<=j;k++)
			f[i][j]=max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k);
	}
}

多重背包优化

int cnt=0;	//记录打包后一共多少件物品 
for(int i=1;i<=n;i++)
{
	int a,b,s;
	cin>>a>>b>>s;
	int k=1;
	while(k<=s)
	//第i件物品,二进制打包 
	{
		cnt++;
		val[cnt]=a*k;
		v[cnt]=b*k;
		s-=k;
		k*=2;
	}
	if(s>0)
	{
		cnt++;
		val[cnt]=a*s;
		v[cnt]=b*s;
	}
}
for(int i=1;i<=cnt;i++)	//将多重背包转化为01背包 
{
	for(int j=V;j>=v[i];j--)
		dq[j]=max(dq[j],dq[j-v[i]]+val[i]);
}

练习

A - Bone Collector HDU - 2602 (01背包)(基础)

Many years ago , in Teddy’s hometown there was a man who was called “Bone Collector”. This man like to collect varies of bones , such as dog’s , cow’s , also he went to the grave …
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 ?
Input
The first line contain a integer T , the number of cases.
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.
Output
One integer per line representing the maximum of the total value (this number will be less than 231).
AC代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long ll;
const int N=1010;
int t,v[N],w[N],n,V;
ll f[N];
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		memset(v,0,sizeof(v));
		memset(w,0,sizeof(w));
		memset(f,0,sizeof(f));
		scanf("%d%d",&n,&V);
		for(int i=1;i<=n;i++)	scanf("%d",&w[i]);
		for(int i=1;i<=n;i++)	scanf("%d",&v[i]);
		for(int i=1;i<=n;i++)
		{
			for(int j=V;j>=v[i];j--)
			{
				f[j]=max(f[j],f[j-v[i]]+w[i]);
//				printf("%d ",f[j]);
			}
				
		}
		printf("%lld\n",f[V]);
	}
	return 0;
}

B - 饭卡 HDU - 2546 (01背包)⭐

电子科大本部食堂的饭卡有一种很诡异的设计,即在购买之前判断余额。如果购买一个商品之前,卡上的剩余金额大于或等于5元,就一定可以购买成功(即使购买后卡上余额为负),否则无法购买(即使金额足够)。所以大家都希望尽量使卡上的余额最少。
某天,食堂中有n种菜出售,每种菜可购买一次。已知每种菜的价格以及卡上的余额,问最少可使卡上的余额为多少。
Input
多组数据。对于每组数据:
第一行为正整数n,表示菜的数量。n<=1000。
第二行包括n个正整数,表示每种菜的价格。价格不超过50。
第三行包括一个正整数m,表示卡上的余额。m<=1000。
n=0表示数据结束。
Output
对于每组输入,输出一行,包含一个整数,表示卡上可能的最小余额。
AC代码:

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdio>
using namespace std;
const int N=1010;
int n,m,val[N],f[N];
int main()
{
	while(scanf("%d",&n)!=EOF&&n)
	{
		memset(val,0,sizeof(val));
		memset(f,0,sizeof(f));
		
		for(int i=1;i<=n;i++)	scanf("%d",&val[i]);
		sort(val+1,val+n+1);	//sort排一下序将价格最高的菜放在最后 
		
		scanf("%d",&m);
		if(m<5)	printf("%d\n",m);	//如果余额小于5那么余额不变 
		else	//如果余额大于5,我们保证最后卡留有五元(及以上)的钱去买最贵的那个菜 
		{
			for(int i=1;i<=n-1;i++)		//价格最高的菜先不做处理 
			{
				for(int j=m-5;j>=val[i];j--)	//计算金额为m-5的余额最多可以买到多少价值的菜 
				{
					f[j]=max(f[j],f[j-val[i]]+val[i]);
				}
			}
			f[m]=f[m-5]+val[n];		//最后最贵的那份菜一定可以买到 
			printf("%d\n",m-f[m]);	//余额减去总支出 
		}
	}
	return 0;
}

C - Piggy-Bank HDU - 1114 (01背包)⭐

Before ACM can do anything, a budget must be prepared and the necessary financial support obtained. The main income for this action comes from Irreversibly Bound Money (IBM). The idea behind is simple. Whenever some ACM member has any small money, he takes all the coins and throws them into a piggy-bank. You know that this process is irreversible, the coins cannot be removed without breaking the pig. After a sufficiently long time, there should be enough cash in the piggy-bank to pay everything that needs to be paid.

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!
Input
The input consists of T test cases. The number of them (T) is given on the first line of the input file. Each test case begins with a line containing two integers E and F. They indicate the weight of an empty pig and of the pig filled with coins. Both weights are given in grams. No pig will weigh more than 10 kg, that means 1 <= E <= F <= 10000. On the second line of each test case, there is an integer number N (1 <= N <= 500) that gives the number of various coins used in the given currency. Following this are exactly N lines, each specifying one coin type. These lines contain two integers each, Pand W (1 <= P <= 50000, 1 <= W <=10000). P is the value of the coin in monetary units, W is it’s weight in grams.
Output
Print exactly one line of output for each test case. The line must contain the sentence “The minimum amount of money in the piggy-bank is X.” where X is the minimum amount of money that can be achieved using coins with the given total weight. If the weight cannot be reached exactly, print a line “This is impossible.”.
AC代码:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long ll;
const int M=0x3f3f3f3f;
const int N=5e5+10;
int t,e,f,n;
int val[N],w[N],dq[N];
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		memset(val,0,sizeof(val));
		memset(w,0,sizeof(w));
		memset(dq,M,sizeof(dq));	//因为求的是最小值,所以刚开始全部赋一个很大的值(后面要用到min) 
		scanf("%d%d",&e,&f);
		scanf("%d",&n);
		for(int i=1;i<=n;i++)	scanf("%d%d",&val[i],&w[i]);
		dq[0]=0;	//当f=e时小猪是实心的 
		for(int i=1;i<=n;i++)
		{
			for(int j=w[i];j<=f-e;j++)
				dq[j]=min(dq[j],dq[j-w[i]]+val[i]);
		}
		if(dq[f-e]==M)	printf("This is impossible.\n");
		else	printf("The minimum amount of money in the piggy-bank is %d.\n",dq[f-e]);
	}
	return 0;
}

D - FATE HDU - 2159 (完全背包)(双限制条件)⭐⭐

最近xhd正在玩一款叫做FATE的游戏,为了得到极品装备,xhd在不停的杀怪做任务。久而久之xhd开始对杀怪产生的厌恶感,但又不得不通过杀怪来升完这最后一级。现在的问题是,xhd升掉最后一级还需n的经验值,xhd还留有m的忍耐度,每杀一个怪xhd会得到相应的经验,并减掉相应的忍耐度。当忍耐度降到0或者0以下时,xhd就不会玩这游戏。xhd还说了他最多只杀s只怪。请问他能升掉这最后一级吗?
Input
输入数据有多组,对于每组数据第一行输入n,m,k,s(0 < n,m,k,s < 100)四个正整数。分别表示还需的经验值,保留的忍耐度,怪的种数和最多的杀怪数。接下来输入k行数据。每行数据输入两个正整数a,b(0 < a,b < 20);分别表示杀掉一只这种怪xhd会得到的经验值和会减掉的忍耐度。(每种怪都有无数个)
Output
输出升完这级还能保留的最大忍耐度,如果无法升完这级输出-1。
思路:
因为这道题有两个限制条件,所以可以建立两个状态方程,一个dq[j]表示忍耐值为j时最多得到的经验值,一个k[j]表示dq[j]状态杀掉的小怪个数。
AC代码:

//完全背包 
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long ll;
const int N=1000;
int n,m,k,s;
int a[N],b[N];
//因为本题有两个限制条件,所以建立两个状态方程 
//dq[i]表示消耗忍耐度为i时获得的最大经验值
//kk[i]表示消耗忍耐度为i时杀掉的小怪数量 
int dq[N],kk[N];
int main()
{
	while(scanf("%d%d%d%d",&n,&m,&k,&s)!=EOF)
	{
		memset(a,0,sizeof(a));
		memset(b,0,sizeof(b));
		memset(dq,0,sizeof(dq));
		memset(kk,0,sizeof(kk));
		for(int i=1;i<=k;i++)	scanf("%d%d",&a[i],&b[i]);
		for(int i=1;i<=k;i++)
		{
			for(int j=b[i];j<=m;j++)	//完全背包从前往后遍历 
			{
				if(dq[j-b[i]]+a[i]>dq[j])
				//如果杀死该小怪获得的经验值大于不杀死该小怪获得的经验值 
				{
					dq[j]=dq[j-b[i]]+a[i];
					kk[j]=kk[j-b[i]]+1;
				}
			}
		}
		int ans=-1;
		for(int i=0;i<=m;i++)	//从杀死小怪个数消耗忍耐度为0开始遍历 
		{
			if(dq[i]>=n&&kk[i]<=s)
			//如果消耗忍耐度为i时获得的经验值大于升级所需经验值且杀死小怪个数小于s 
			{
				ans=m-i;
				break;
			}
		}
		printf("%d\n",ans);
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值