DP揹包問題小結(01揹包,完全揹包,需恰好裝滿或不需,一維DP、二維DP)
阿新 • • 發佈:2019-02-10
1)
揹包基礎,先以01揹包、求揹包所裝物品價值之和的最大值、不要求恰好裝滿時,易於理解的二維DP陣列儲存為例:
#include <iostream> #include <string.h> using namespace std; int dp[1010][1010]; int vp[1010]; int wp[1010]; int main() { int kase;cin>>kase; while(kase--){ memset(dp,0,sizeof(dp)); int n,v;cin>>n>>v; int v1,w1; for(int i=1;i<=n;i++){ cin>>vp[i]; } for(int i=1;i<=n;i++){ cin>>wp[i]; } for(int i=1;i<=n;i++){ v1=vp[i];w1=wp[i]; for(int j=0;j<=v;j++){//正序、逆序,皆可,因為有第一唯i的存在,保證了每一次更新都保證不會放入相同的物品 if(j>=w1) dp[i][j]=max(dp[i-1][j],dp[i-1][j-w1]+v1); else dp[i][j]=dp[i-1][j];//假如第i個因為體積原因放不進去,那麼d第i個的狀態繼承第i-1的狀態(如果不操作,則為初始值0) } } cout<<dp[n][v]<<endl; } }
2)以01揹包,採用方便寫的一維DP陣列,求揹包所裝物品價值之和最大值,不要求恰好裝滿揹包為例:
//三個小分支:
//改為求完全揹包時(¥1處有變化)
//改為求揹包所裝物品價值之和的最小值時,(#1與#2處程式碼,有變化)
//改為要求恰好裝滿揹包時(@1處程式碼有變化)
#include <iostream> #include <string.h> using namespace std; const int inf=100000000;//#1求揹包所裝物品價值之和的最大值時,inf為負無窮,如果求最小值,inf為正無窮,具體寫幾個0,看題目要求的限制 int main() { int kase;cin>>kase; int value[1010]; int volume[1010]; int dp[1010]; while(kase--){ int n;cin>>n; int bag;cin>>bag; memset(dp,0,sizeof(dp));/*@1不要求恰好裝滿揹包時,則dp[0],和dp[1...n]都置為0。@2為替換程式碼。理由在@2下面。 /* //@2要求恰好裝滿揹包時,則dp[0]=0,dp[1...n]都為正無窮或者負無窮(取決於題目是求最大值還是最小值,看#1處) dp[0]=0; for(int i=1;i<=bag;i++) dp[i]=inf; */ /* 當我們把1到n初始化為無窮值,只有0是初始化為0時,這時其他不變,一切更新照常,所不同的是,只有以0作為基礎的更新的值處於正常範圍,而其他值都因為初始無窮的原因,每一次更新後的數都大於等於正無窮(如果取最小值則每一次更新後還是正無窮,如果取最大值,沒一次更新都大於正無窮),那麼區分是否能恰好裝滿,只需要看最後一個狀態的值是不是正常範圍內的值即可。舉例如下: f為負無窮,g為正無窮,sum為揹包總體積,v為物品價值,w為物品體積 sum:8 v w 3 2 4 3 w:0->n 要求恰好裝滿時,將0為0,將1,2,3...9都非0的初始值,又因為取最大值,所以非0的初始值是負無窮: 初始值: 0 1 2 3 4 5 6 7 8 9 0 f f f f f f f f f 0+2 f+2 f+2 2+2 f+2+2 f+2+2 2+2+2 0+3 f+3 f+3 2+3 3+3 f+3+3 ( 先放大數或者先放小數並沒有區別,舉例如下,先放v==4,w==3 初始值: 0 1 2 3 4 5 6 7 8 9 0 f f f f f f f f f 0+3 f+3 f+3 f+3 3+3 f+3+3 0+2 f+2 f+2 2+2 f+2+3 f+2+3 2+2+2 ) 不要求恰好裝滿時,此時0到9全部為0,不論取最大值還是取最小值,都是0做初始值: 初始值: 0 1 2 3 4 5 6 7 8 9 0 0 0 0 0 0 0 0 0 0 2 2 2 2+2 2+2 2+2 2+2+2 */ for(int i=0;i<n;i++){ cin>>value[i]; } for(int i=0;i<n;i++){ cin>>volume[i]; } for(int i=0;i<n;i++){ for(int j=bag;j>=volume[i];j--){//*¥1,01揹包每一種物品只能放一次,則此處要j從大到小,採用逆序(以保證,每一次更新放入的物品都不重複,都是在上一個物品放置後的情況下更新的),而完全揹包,j從小到大,正序,這樣使一種物品可以重複放置。兩者區別具體講,當j逆序由大到小如由j==3到j==2變化時,假設volume[i]=1,j-volume[i]也是逐漸減小,依次對應為,2、1,那麼狀態轉移方程就是dp[3]=max(dp[3],dp[2]+value[i]);dp[2]=max(dp[2],dp[1]+value[i]);如果是完全揹包,j正序由小變大,那麼dp[2]=max(dp[2],dp[1]+value[i]);dp[3]=max(dp[3],dp[2]+value[i]);最後一個式子中,進行比較所用的dp[2]剛剛被更新過,是現在這個物品放置一次、更新後的情況而非上一個物品放置後的情況!所以使同一個物品可以重複放置!*/ dp[j]=(dp[j]>dp[j-volume[i]]+value[i])?dp[j]:dp[j-volume[i]]+value[i];//#2,此處取大的一方更新原來的數,如果求最小值自然取小的一方更新原來的數。例如下面 /* dp[j]=(dp[j]<dp[j-volume[i]]+value[i])?dp[j]:dp[j-volume[i]]+value[i];// */ } } cout<<dp[bag]<<endl; } }
3)練習題目
01揹包、不要求恰好裝滿、求最大值,入門題,hdu2602;
完全揹包、要求恰好裝滿、求最小值,入門題,hdu1114;
hdu2602 AC程式碼:
#include <iostream> #include <string.h> #include <map> using namespace std; //map <int,int> bone; int main() { int kase;cin>>kase; int value[1010]; int volume[1010]; int dp[1010]; while(kase--){ int n;cin>>n; int bag;cin>>bag; memset(dp,0,sizeof(dp)); //int value,volume; for(int i=0;i<n;i++){ cin>>value[i]; //bone[value]=volume; } for(int i=0;i<n;i++){ cin>>volume[i]; } for(int i=0;i<n;i++){ for(int j=bag;j>=0;j--){ if(j>=volume[i]){ dp[j]=(dp[j]>dp[j-volume[i]]+value[i])?dp[j]:dp[j-volume[i]]+value[i]; } //else //dp[j]=dp[j]; } } cout<<dp[bag]<<endl; } }
hdu1114 AC程式碼:
#include <iostream>
#include <string.h>
using namespace std;
struct coin{
int value;
int weight;
}coins[510];
int dp[10010];
//求最小值,用正無窮:
long long int inf=100000000;
int main()
{
int kase;cin>>kase;
while(kase--){
int emp,full;cin>>emp>>full;
int volume=full-emp;
int n;cin>>n;
//恰好裝滿,不同初始值:
dp[0]=0;
for(int i=1;i<=volume;i++){
dp[i]=inf;
}
for(int i=0;i<n;i++){
cin>>coins[i].value;
cin>>coins[i].weight;
}
//完全揹包,正序:
for(int i=0;i<n;i++){
for(int j=coins[i].weight;j<=volume;j++){
dp[j]=min(dp[j],dp[j-coins[i].weight]+coins[i].value);
}
}
if(dp[volume]==inf)
cout<<"This is impossible."<<endl;
else{
printf("The minimum amount of money in the piggy-bank is %d.\n",dp[volume]);
}
}
}