01揹包問題
參考:
[https://blog.csdn.net/qq_38410730/article/details/81667885]
[https://www.cnblogs.com/arsenalfaninecnu/p/8945548.html])
什麼是01揹包問題?
01揹包問題描述:有編號分別爲a,b,c,d的N=4件物品,它們的重量w分別是2,3,4,5,它們的價值v分別是3,4,5,6,每件物品數量只有一個,現在給你個承重爲M=8的揹包,如何讓揹包裏裝入的物品具有最大的價值總和?總和是多少?
解題思路
結題思路是把該問題分解爲一個一個的小問題,一步步的通過小問題的最優解,最終得到大問題的最優解。在這個問題中有兩個維度的變量,一是揹包的容量M,二是物品的種類數N,假如M=1,只有一種物品 a,最優解是什麼,有a,b兩種物品是最優解是什麼?以此類推。
狀態轉移方程怎麼列?
1.首先,當物品種類N一定時,揹包容量越大,那麼最後的結果一定大於等於原來的值
2.其次,當揹包容量M一定時,物品種類N越多,最後的記過一定大於等於原來的值。
3.那麼,也就是說,我們想要求取的最大值也即是當M和N最大的時候的值。
假如當前有a和b兩種,M=1時,考慮a是否能夠放入,取得最大值,然後考慮a和b是否能放入,最終的情況可能只有a,或只有b或者ab都有。
對於b來說:
判斷b能否單獨放進揹包:
—如果不能,那麼備選爲a,b時最大值,等於備選只有a時的最大值(因爲b是放不進揹包的)。
—如果能,即b能夠放進去,還有兩種可能(即將b放進揹包,和不將b放進揹包),對這兩種可能性,要取最大值:
---------最終將b放進去(注:此時物品a是否被放進揹包是未知的,原因是:剩餘的揹包容量可能不足以放進物品a,即要在剩餘可選物品裏找出最優解。
---------最終沒有將b放進去(因爲後面可能有比b更合適的物品放進去),此時最大值等於備選只有a時的最大值
引自原文
簡單來說我們要思考的是第i個物品是否放進揹包
c++代碼如下:
if(j>=w[i])
dp[i][j]=max(dp[i-1][j-w[i]]+v[i],dp[i-1][j]);
//dp[i][j]存儲i個物品,容量爲j時的最大值
else dp[i][j]=dp[i-1][j];
解題代碼如下:
#include<iostream>
using namespace std;
int m,n;//n是物品種類數,m是揹包容量
int w[100],v[100];
long long dp[100][100];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>w[i]>>v[i];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(j>=w[i]) dp[i][j]=max(dp[i-1][j-w[i]]+v[i],dp[i-1][j]);
else dp[i][j]=dp[i-1][j];
}
//上面的代碼可以計算出最值,輸出動態規劃表
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)
cout<<dp[i][j]<<" ";
cout<<endl;
}
cout<<dp[n][m];
return 0;
}
回溯最優解
求出最大值後我們還要找出最後那幾個物品被放進了揹包,用回溯的方法來尋找最後解,思路如下
首先,判斷dp[i][j]與dp[i-1][j-w[i]]+v[i]是否相等:
– 如果相等,則說明第i個物品沒有放進揹包,則繼續回溯(i-1,j);、
– 否則就是進了揹包,則回溯到(i-1,j-w[i])
定義函數如下:
void findMax(int i,int j){
if(i>=0){
if(dp[i][j]==dp[i-1][j]){
//如果滿足這個條件,說明第i個沒有放進揹包
item[i]=0;
//那麼就回溯到i-1,j
findMax(i-1,j);
}
else if(j-w[i]>=0 && dp[i][j]==dp[i-1][j-w[i]]+v[i])
{
item[i]=1;
//如果放進揹包就回溯到下面
findMax(i-1,j-w[i]);
}
}
完整代碼:
#include<iostream>
using namespace std;
int m,n;//n是物品種類數,m是揹包容量
int w[100],v[100];
int item[100];//標記是否被選中
long long dp[100][100];
//回溯最優解
void findMax(int i,int j){
if(i>=0){
if(dp[i][j]==dp[i-1][j]){
//如果滿足這個條件,說明第i個沒有放進揹包
item[i]=0;
//那麼就回溯到i-1,j
findMax(i-1,j);
}
else if(j-w[i]>=0 && dp[i][j]==dp[i-1][j-w[i]]+v[i])
{
item[i]=1;
//如果放進揹包就回溯到下面
findMax(i-1,j-w[i]);
}
}
//一直遍歷直到i=0爲止
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>w[i]>>v[i];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(j>=w[i]) dp[i][j]=max(dp[i-1][j-w[i]]+v[i],dp[i-1][j]);
else dp[i][j]=dp[i-1][j];
}
//上面的代碼可以計算出最值,輸出動態規劃表
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)
cout<<dp[i][j]<<" ";
cout<<endl;
}
//尋找最優解並輸出
findMax(n,m);
for(int i=1;i<=n;i++) cout<<item[i]<<" ";
return 0;
}