1. 程式人生 > >【HDU - 2546】飯卡 (dp,0-1揹包,貪心思想)

【HDU - 2546】飯卡 (dp,0-1揹包,貪心思想)

電子科大本部食堂的飯卡有一種很詭異的設計,即在購買之前判斷餘額。如果購買一個商品之前,卡上的剩餘金額大於或等於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;
}