完全背包系列

提到完全背包,有下列伪代码:

for i=1..N
    for v=0..V
        f[v]=max{f[v],f[v-cost]+weight}
和01背包相似,但与01背包不同的地方是0..V,而不是V..0,这个是为了保证每一件物品的数量是无限的,为了理解更深刻真切,结合一道裸完全背包题来解释:

杭电1248  传送门:http://acm.hdu.edu.cn/showproblem.php?pid=1248


Problem Description
不死族的巫妖王发工资拉,死亡骑士拿到一张N元的钞票(记住,只有一张钞票),为了防止自己在战斗中频繁的死掉,他决定给自己买一些道具,于是他来到了地精商店前.

死亡骑士:"我要买道具!"

地精商人:"我们这里有三种道具,血瓶150块一个,魔法药200块一个,无敌药水350块一个."

死亡骑士:"好的,给我一个血瓶."

说完他掏出那张N元的大钞递给地精商人.

地精商人:"我忘了提醒你了,我们这里没有找客人钱的习惯的,多的钱我们都当小费收了的,嘿嘿."

死亡骑士:"......"

死亡骑士想,与其把钱当小费送个他还不如自己多买一点道具,反正以后都要买的,早点买了放在家里也好,但是要尽量少让他赚小费.

现在死亡骑士希望你能帮他计算一下,最少他要给地精商人多少小费.
 

Input
输入数据的第一行是一个整数T(1<=T<=100),代表测试数据的数量.然后是T行测试数据,每个测试数据只包含一个正整数N(1<=N<=10000),N代表死亡骑士手中钞票的面值.

注意:地精商店只有题中描述的三种道具.
 

Output
对于每组测试数据,请你输出死亡骑士最少要浪费多少钱给地精商人作为小费.
 

Sample Input
 
  
2 900 250
 

Sample Output
 
  
0 50

先给出代码:

<span style="font-size:18px;">#include<stdio.h>
#include<algorithm>
using namespace std;
int T;
int N;
int f[11111];
int main(){
	int i, j;
	int goods[4] = {0, 150, 200, 350};
	    scanf("%d",&T);
		while(T--){
	    memset(f,0,sizeof(f));
		scanf("%d",&N);
	for( i = 1; i <= 3; i++ ){
		for( j = goods[i]; j <= N; j++ ){
			f[j] = max(f[j], f[j-goods[i]] + goods[i]);
		}
	}
	printf("%d\n",N-f[N]);
	}
	return 0;
}</span>

注意核心的部分:

<span style="font-size:18px;">for( i = 1; i <= 3; i++ ){
		for( j = goods[i]; j <= N; j++ ){
			f[j] = max(f[j], f[j-goods[i]] + goods[i]);
		}
	}</span>
  我们先看i=1时,f[j]的变化,f[150]~f[299]全都变成了150,意思就是如果拥有的钱数是150~299,并且全用来买物品1,我们可以买1件,花的钱是150元,而如果钱数是300~449,f[300]~f[449]是300,即此时买物品1最多可以买两件,以此类推,这一趟循环中,我们可以看出物品1的数量可以是无限多的,主要还是取决于你有多少钱,而如果到了i=2时,也就是以买物品1时的方案为基础,再看看能否有在拥有同样的钱的情况下,找到消费更高的策略,所以也不难理解为什么要0~V,但如果我们采用了V~0,我们取最大值是倒着取,i=1时则永远是在拿0和0+150取最大值,本质上是只取了一件物品1。

理解了完全背包的算法,再加个练习。

杭电4508 传送门:http://acm.hdu.edu.cn/showproblem.php?pid=4508

Problem Description
  对于吃货来说,过年最幸福的事就是吃了,没有之一!
  但是对于女生来说,卡路里(热量)是天敌啊!
  资深美女湫湫深谙“胖来如山倒,胖去如抽丝”的道理,所以她希望你能帮忙制定一个食谱,能使她吃得开心的同时,不会制造太多的天敌。
  当然,为了方便你制作食谱,湫湫给了你每日食物清单,上面描述了当天她想吃的每种食物能带给她的幸福程度,以及会增加的卡路里量。
Input
  输入包含多组测试用例。
  每组数据以一个整数n开始,表示每天的食物清单有n种食物。
  接下来n行,每行两个整数a和b,其中a表示这种食物可以带给湫湫的幸福值(数值越大,越幸福),b表示湫湫吃这种食物会吸收的卡路里量。
  最后是一个整数m,表示湫湫一天吸收的卡路里不能超过m。

  [Technical Specification]
  1. 1 <= n <= 100
  2. 0 <= a,b <= 100000
  3. 1 <= m <= 100000

Output
  对每份清单,输出一个整数,即满足卡路里吸收量的同时,湫湫可获得的最大幸福值。

Sample Input
 
  
3 3 3 7 7 9 9 10 5 1 1 5 3 10 3 6 8 7 5 6

Sample Output
 
  
10 20
纯完全背包,直接写上代码:

<span style="font-size:18px;">#include<stdio.h>
#include<algorithm>
using namespace std;
int n;
int a[111], b[111];
int m;
int f[111111];
int main(){
	int i, j;
	while(scanf("%d",&n)!=EOF){
		for( i = 1; i <= n; i++ ){
			scanf("%d%d",&a[i],&b[i]);
		}
		memset(f,0,sizeof(f));
		scanf("%d",&m);
		for( i = 1; i <= n; i++ ){
			for( j = b[i]; j <= m; j++ ){
				f[j] = max(f[j], f[j-b[i]] + a[i]);
			}
		}
		printf("%d\n",f[m]);
	}
	return 0;
}</span>

  找最小值的完全背包问题:

  杭电1114 传送门:http://acm.hdu.edu.cn/showproblem.php?pid=1114

  

Problem Description
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.". 

Sample Input
 
  
3 10 110 2 1 1 30 50 10 110 2 1 1 50 30 1 6 2 10 3 20 4
 Sample Output
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.
这道题的难点,在于不但要找到一个总价值最低的结果,还要确保这个结果的合理性,即硬币总重量要与这个结果对应。
注意:
1、若想求最小值,要注意的是必须用无穷大初始化数组,同时f[0]要初始化为0
2、若要保证最终结果与总重量对应,其实不难,只需要验证dp[weight]是不是无穷大即可,因为当每一步都是求最小值时,到最后会求出正好装满背包时的状态,注意:求最大值得到的结果不一定是装满背包的,只是保证价值最大的 原因是因为,最小值的dp数组,每次值的变动,都与前某一个值变动过的dp元素对接,所以如果最后一个元素的值不是无穷大,必然是有一系列对接关系才能造成的,比如 dp[10] = min(dp[10],dp[5]+5) 但d[5]是正无穷呀,那么d[5]+5只会比d[10]大,不会比d[10]小,因为我们 默认d[10]还没动过,所以这种情况就不会变动dp元素的值,只有什么情况呢? 只有当d[5]也成功地赋值,d[10]才能成功赋值,而假如weight是100,d[100] 是由很多很多个像连接d[10]和d[5]这样的w[i]来连接起来的,所以d[100]就是 某种w[i]之和恰好为100的情况,而这种情况,恰好也正是我们要找的最小值, 而最大值的dp数组就不一样了,每次值的变动,都十分轻松的,比如dp[10] = max(dp[10],dp[5]+5) 显然,没有赋值过的dp[10]挡不住dp[5]+5的诱惑,所以每一个这种情况都赋值成功, 所以我们就不能保证最后的dp[weight]的值是背包正好装满 3、少了打个.就wa啦,真是无语无语再无语!!!
<pre name="code" class="cpp">#include<stdio.h>
#include<algorithm>
using namespace std;
int T, E, F, N;
int P[555], W[555];
int f[111111];
# define INF 1111111
int main(){
	int i, j;
	int weight;
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&E,&F);
	    weight = F - E;     
		 scanf("%d",&N);
		 for( i = 0; i <= weight; i++ )
			 f[i] = INF;
		 f[0] = 0;
		 for( i = 1; i <= N; i++ ){
			 scanf("%d%d",&P[i],&W[i]);
		 }
		 for( i = 1; i <= N; i++ ){
			 for( j = W[i]; j <= weight; j++ ){
				 f[j] = min(f[j], f[j-W[i]] + P[i]);
			 }
		 }
		 if(f[weight] == INF)
		 printf("This is impossible.\n");
		 else
	         printf("The minimum amount of money in the piggy-bank is %d.\n",f[weight]);
	}
	return 0;
}

最后是有关方法总数的完全背包问题:
杭电1284 
Problem Description
在一个国家仅有1分,2分,3分硬币,
将钱N兑换成硬币有很多种兑法。请你编程序计算出共有多少种兑法。
Input
每行只有一个正整数N,N小于32768。
Output
对应每个输入,输出兑换方法数。
Sample Input
2934
12553
Sample Output
718831
13137761
 
  
这个问题,是要求方法总数,先看代码,再解释:
<pre name="code" class="cpp">#include<stdio.h>
#include<algorithm>
using namespace std;
int N;
int f[33333];
int main(){
	int i, j;
	int money[4] = {0, 1, 2, 3};
	while(scanf("%d",&N)!=EOF){
		memset(f,0,sizeof(f));
		f[0] = 1;
		for( i = 1; i <= 3; i++ ){
			for( j = money[i]; j <= N; j++ ){
		  f[j] = max(f[j], f[j-money[i]] + f[j]);
			}
		}
		printf("%d\n",f[N]);
	}
	return 0;
}
 
  
参考背包九讲:
<pre name="code" class="cpp" style="font-family: 'Courier New', Courier, monospace; background-color: rgb(255, 255, 255);"><pre name="code" class="cpp">for( i = 1; i <= 3; i++ ){
			for( j = money[i]; j <= N; j++ ){
		  f[j] = max(f[j], f[j-money[i]] + f[j]);
			}
		}
 
  
 
  
这样做可行的原因在于状态转移方程已经考察了所有可能的背包组成方案。<span lang="EN-US"><o:p></o:p></span>
 
  
 
  
                                                                                             
 
 

猜你喜欢

转载自blog.csdn.net/sinyusin/article/details/52203338