【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;
}