【HDU - 2546】饭卡 (dp,0-1背包,贪心思想)

版权声明:欢迎学习我的博客,希望ACM的发展越来越好~ https://blog.csdn.net/qq_41289920/article/details/81878543

电子科大本部食堂的饭卡有一种很诡异的设计,即在购买之前判断余额。如果购买一个商品之前,卡上的剩余金额大于或等于5元,就一定可以购买成功(即使购买后卡上余额为负),否则无法购买(即使金额足够)。所以大家都希望尽量使卡上的余额最少。 
某天,食堂中有n种菜出售,每种菜可购买一次。已知每种菜的价格以及卡上的余额,问最少可使卡上的余额为多少。 

Input

多组数据。对于每组数据: 
第一行为正整数n,表示菜的数量。n<=1000。 
第二行包括n个正整数,表示每种菜的价格。价格不超过50。 
第三行包括一个正整数m,表示卡上的余额。m<=1000。 

n=0表示数据结束。 

Output

对于每组输入,输出一行,包含一个整数,表示卡上可能的最小余额。

Sample Input

1
50
5
10
1 2 3 2 1 1 2 3 2 1
50
0

Sample Output

-45
32

解题报告:

   这道题也可以说是刚刚接触背包问题的小菜鸟必做的一道题了。解出这道题的关键在于怎么处理这个5元。我们的处理方式是,对余额-5 并将最贵的菜踢出去,然后对这个新余额和剩余的n-1个物品进行0-1背包,解出能购买的最大价值。

   这种做法的正确性在此稍作说明:

            首先因为我们需要余额最小,所以直观上我们肯定想把最贵的放在最后一个买,这样直接拉开差距。但是有的同学会反过来想,那我万一是把最贵的物品放到背包里面去跑,然后这样可能更能填满呢,(即最能使余额接近5元)然后此时我再拿一个第二贵的去使余额变为负数,这样有可能更好啊。。嗯,这样想是没有问题的,但是你要这么想啊,既然你这样,那还是说明用到了最贵的物品,只是放到背包中了,其实相当于背包中放物品的次序换了一下,该放的物品还是那些物品,所以并不会影响最终的答案啊,而你如果在n-1个物品背包的时候没有用到最贵的物品,那何必要让第二贵的去当最后一个使余额变负数的物品呢?用最贵的岂不是更好?

           综上,我们得出结论,把最贵的物品贪心出来,然后对剩下的物品进行0-1背包的做法是正确的。

为了加深对本题的理解,这里附上三种方法,三种代码:

AC代码1:(普通的0-1背包)

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m;
int v[1000 +5];
int dp[1000 + 5];
int main()
{
    while(~scanf("%d",&n) ) {
        if(n == 0 ) break;
        memset(dp,0,sizeof(dp));
        for(int i = 1; i<=n; i++) {
            cin>>v[i];
        }
        cin>>m;
        sort(v+1,v+n+1);
        m-=5;
        if(m < 0) {
            printf("%d\n",m+5);
            continue;
        }
        for(int i = 1; i<n; i++) {
            for(int j = m; j>=v[i]; j--) {
                dp[j] = max(dp[j], dp[j - v[i] ] + v[i]);
            }
        }
 //       printf("**%d***%d\n",m,dp[n-1],dp[n]);
        printf("%d\n",m+5 - dp[m] - v[n]);

    }

    return 0 ;
}

AC代码2:(当成恰好花费做的)

#include<bits/stdc++.h>

using namespace std;
int dp[1000 + 5];
int v[1000 + 5];
int n,m;
int main()
{
    while(~scanf("%d",&n) ) {
        if(n == 0 ) break;
        for(int i = 1; i<=n; i++) {
            scanf("%d",&v[i]);
        }
        scanf("%d",&m);
        m-=5;
        sort(v+1,v+n+1);
        int tmp = v[n];
        if(m < 0) {
            printf("%d\n",m+5);continue;
        }
		for(int i = 1; i<=1001; i++) dp[i] = - 0x3f3f3f3f;//这里刚开始写成i<=2000,把v都覆盖成了-INF,所以直接错了,要注意啊!从汇编的角度也可以理解这件事情。。。自己想想吧。,
		dp[0]=0;

        for(int i = 1; i<n; i++) {
            for(int j = m; j>=v[i]; j--) {
                dp[j] = max(dp[j],dp[j-v[i]] + v[i]);
            }
        }
        int res = 0;
		for(int i = m; i>=0; i--) {
			if(dp[i]>=0) {
				res = dp[i];break;
			}
		}
        printf("%d\n", m + 5 -res - tmp );

    }
    return 0;
}

 AC代码3:(因为单个物品的最大花费是50元,所以我在余额上统一+50元,最后在减去这50元,这做法就不需要排序贪心了)

#include<bits/stdc++.h>
#define nn 1100
#define inff 0x3fffffff
using namespace std;

int n,m;
int a[nn];
bool dp[nn];
int main() {
	int i,j;
	while(scanf("%d",&n),n) {
		for(i=1; i<=n; i++) {
			scanf("%d",&a[i]);
		}
		scanf("%d",&m);
		sort(a+1,a+n+1);
		memset(dp,false,sizeof(dp));
		dp[m+50]=true;
		for(i=1; i<=n; i++) {
			for(j=0; j<=m+50; j++) {
				if(j+a[i]-50>=5&&j+a[i]<=m+50)
					dp[j]=dp[j+a[i]]?true:dp[j];
			}
		}
		for(i=0; i<=m+50; i++)
			if(dp[i])
				break;
		printf("%d\n",i-50);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_41289920/article/details/81878543
今日推荐