HDOJ 1114 Piggy-Bank
題目大意:已知存錢罐豬的重量和存錢罐豬以及罐內硬幣的總重量,已知每種硬幣的面值與重量,每種硬幣數量無限,求罐內硬幣的最小面值。
輸入。T:總的案例個數,針對每個案例有E,F:罐的重量以及罐+硬幣的總重量。N:硬幣的種類數,之後有N行,每行為P:硬幣的面值,以及W:硬幣的重量。
這個題目要求硬幣的總重量應該恰好等於F-E,如果根據給定的硬幣資料,硬幣的重量無法恰好等於F-E,則輸出Impossible。如果硬幣重量可以恰好等於F-E,則輸出可能的硬幣最小的價值。
問題為完全揹包的問題,F-E可以看做揹包的重量,硬幣可以看做物品。設f[i](j)為硬幣總重量為i時,從前j種硬幣選擇,最小的面值。則此時有兩種選擇:1、不選擇第j種硬幣,則f[i](j)=f[i](j-1)。2、選擇一個第j種硬幣,則擁有了P[j]的價值,硬幣總重量變為了i-W[j],由於硬幣數量無限,則接下來還是從前j種硬幣選擇,獲取硬幣的最小面額為f[i-W[j] ] + P[j]。則此時f[i] (j) = min { f[i](j-1) , f[i-W[j] ] + P [j] }。
如果用一維陣列求解,則根據完全揹包的講解,在兩層for迴圈中(硬幣總重量的for迴圈i以及從前j種硬幣選擇的for迴圈j)需要全部正序遍歷。我在看到完全揹包講解的時候並沒有理解為什麼都要正序(0-1揹包中是逆序),也是通過一個具體的問題,手動運行了虛擬碼以後理解得更加深刻一些。則我們求解的虛擬碼可以表示為:
for(int i=1;i<=weight;i++){ for(int j=1;j<=N;j++){ if(i-W[j]>=0){ f[i]=min(f[i],f[i-W[j]]+P[j]); } } }
設硬幣的總重量(F-E)為weight,則我們的最終目標就是求解f[weight]。
這裡還有一個問題,就是f的初值定位多少的問題。我的求解思路是這樣的,由於需要求解面值的最小值,那麼一開始我就把f定為一個無窮大,這個時候當我求解出一個更小的解時,就進行替換,一直到最後把最小的值進行替換。那麼,題目中的正無窮究竟是多少呢?題目中要求1<=E<=F<=10000,1<=P<=50000,1<=W<=10000,那麼極端的情況下,就是硬幣總重量為10000(近似),有一個重量為1的硬幣,面值為50000,此時最大的面值為10000*50000,我們求解出的任何一個面值都會比它小,那麼我們就可以將f初始化為這個值。
注意f[0]需要初始化為0,因為可能存在這樣的情況:硬幣總重量為1,有一種重量為1,面值為2的硬幣。那麼,此時f[1]=min ( f[1], f[1-1]+P[1])求解出來為2。這些都確定下來,就可以寫出整個題目的程式了:
#include <iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int f[10005];//最小面值
int P[502],W[502];
const int max_value=10000*50000;
void fInit(int *f);
int main(){
int T;//cases總數
int E,F;//豬的重量,總重量
int N;//硬幣種數
int weight;//硬幣的總重量
cin>>T;
for(int i=1;i<=T;i++){
fInit(f);
cin>>E>>F;
cin>>N;
for(int j=1;j<=N;j++){
cin>>P[j]>>W[j];
}
weight=F-E;
for(int k1=1;k1<=weight;k1++){
for(int k2=1;k2<=N;k2++){
//硬幣重量k1,
if(k1-W[k2]>=0){
f[k1]=min(f[k1],f[k1-W[k2]]+P[k2]);
}
}
}
if(f[weight]==max_value){
cout<<"This is impossible."<<endl;
}else{
cout<<"The minimum amount of money in the piggy-bank is "<<f[weight]<<"."<<endl;
}
}
}
void fInit(int * f){
for(int i=1;i<10005;i++) f[i]=max_value;
f[0]=0;
}