揹包問題-01揹包,完全揹包,多重揹包
-
揹包問題-01揹包,完全揹包,多重揹包
-
01揹包:
概念:
有Goods_Num件物品,MAX_Volume的最大裝載量,每種物品只有一件,每種物品都有對應的重量或者說體積Volume[i],價值Value[i],求解裝包的最大價值
狀態轉移方程推導:
假設目前已經有 i-1件物品裝在容量為 j 的揹包中,並且得到最大價值Package[ i-1 ][ j ],當前裝第i件,那麼討論分兩個角度,如果第i個物品裝不入揹包,那麼最大價值不變。如果第i個物品可以放入,那麼如果揹包容量為 j-Volume[i]的時候的最大裝包價值加上第i個物品價值大於Package[i-1][j],那麼Package[i][j]最大價值就更新了,否則就不裝i
得到狀態轉移方程:
a. Volume[ i ] > j :Package[ i-1 ][ j ]
b. j >= Volume[ i ] :max( Package[ i-1 ][ j-Volume[i] ] +Value[ i ], Package[ i-1 ][ j ] )
實現程式碼:
#include<iostream> #include<algorithm> #include<memory> using namespace std; #define MAX_SIZE 1024 int Volume[MAX_SIZE]; /*每個物品所佔空間(或重量)*/ int Value[MAX_SIZE]; /*物品價值*/ int Package[MAX_SIZE][MAX_SIZE]; /*未優化,Package[i][j] 表示前i個物品放入容量為j的購物車的最大價值*/ int MAX_Volume, Goods_Num; /*最大容量,物品數量*/ void Init() { memset(Package, 0, sizeof(Package)); } void _01Package() { for (int i = 1; i <= Goods_Num; i++) { for (int j = 1; j <= MAX_Volume; j++) { if (j < Volume[i]) /*裝不下第i個物品*/ Package[i][j] = Package[i - 1][j]; else Package[i][j] = max(Package[i - 1][j], Package[i - 1][j - Volume[i]] + Value[i]); /*將第i個物品放在容量為j - Volume[i](已得的最優情況)的購物車裡*/ } } } void Print_Res() /*列印結果*/ { cout << "The max value of Package:" << Package[Goods_Num][MAX_Volume] << endl; int i, j = MAX_Volume; cout << "Goods in Package: "; for (int i = Goods_Num; i > 0; i--) { if (Package[i][j] > Package[i - 1][j]) { cout << i<<" "; j -= Volume[i]; } } cout << endl; }
優化:
在設計演算法時會發現,上一層的資料在本層中並沒有修改,而是作為狀態的記錄,而且本層當前Index只會用到上一層Index之前的資料,所以可以降成一維,稱為滾動陣列。並且要求每次都重後往前遍歷,避免重複疊包
優化程式碼:
#include<iostream> #include<algorithm> #include<memory> using namespace std; #define MAX_SIZE 1024 int Volume[MAX_SIZE]; /*每個物品所佔空間(或重量)*/ int Value[MAX_SIZE]; /*物品價值*/ int _Package[MAX_SIZE]; /*優化後,Package[i]表示容量i的購物車所獲得的最大價值*/ int MAX_Volume, Goods_Num; /*最大容量,物品數量*/ void Init() { memset(_Package, 0, sizeof(_Package)); } void _01Package_Optimize() /*優化演算法*/ { for (int i = 1; i <= Goods_Num; i++) for (int j = MAX_Volume; j >= Volume[i]; j--) /*每個物品最多隻能裝一次,從後開始遍歷防止最優情況疊加*/ _Package[j] = max(_Package[j], _Package[j - Volume[i]] + Value[i]); } void Print_Res_Optimize() { cout << "The max value of Package:" << _Package[MAX_Volume] << endl; }
-
完全揹包:
概念:
有Goods_Num件物品,MAX_Volume的最大裝載量,每種物品數量無限,每種物品都有對應的重量或者說體積Volume[i],價值Value[i],求解裝包的最大價值
狀態轉移方程推導:
與01揹包的思路基本相似,不過重要的一點是,每種物品是無限的,所以揹包可以多填
得狀態轉移方程:
a. Volume[ i ] > j :Package[i-1][ j ]
b. j>=Volume[ i ] :max( Package[ i-1 ][ j-m*Volume[ i ] ] +m*Value[ i ], Package[ i-1 ][ j ] )
實現程式碼:
#include<iostream>
#include<algorithm>
#include<memory>
using namespace std;
#define MAX_SIZE 1024
int Volume[MAX_SIZE]; /*每個物品所佔空間(或重量)*/
int Value[MAX_SIZE]; /*物品價值*/
int Package[MAX_SIZE][MAX_SIZE]; /*未優化,Package[i][j] 表示前i個物品放入容量為j的購物車的最大價值*/
int MAX_Volume, Goods_Num; /*最大容量,物品數量*/
void Init()
{
memset(Package, 0, sizeof(Package));
}
void Total_Package()
{
for (int i = 1; i <= Goods_Num; i++)
{
for (int j = MAX_Volume; j >= Volume[i]; j--) /*重後往前避免單物品多填影響後序的填包,後填不影響前*/
{
int k = j / Volume[i]; /*最多再填k個i物品*/
for (int m = 0; m <= k; m++)
Package[i][j] = max(Package[i - 1][j - m * Volume[i]] + m * Value[i], Package[i - 1][j]);
}
}
}
優化程式碼:
#include<iostream>
#include<algorithm>
#include<memory>
using namespace std;
#define MAX_SIZE 1024
int Volume[MAX_SIZE]; /*每個物品所佔空間(或重量)*/
int Value[MAX_SIZE]; /*物品價值*/
int _Package[MAX_SIZE]; /*優化後,Package[i]表示容量i的購物車所獲得的最大價值*/
int MAX_Volume, Goods_Num; /*最大容量,物品數量*/
void Init()
{
memset(_Package, 0, sizeof(_Package));
}
void Total_Package_Optimize() /*優化演算法*/
{
for (int i = 1; i <= Goods_Num; i++)
{
for (int j = Volume[i]; j <= MAX_Volume; j++)/*一維,從前往後dp才能實現一個物品的多填*/
_Package[j] = max(_Package[j], _Package[j - Volume[i]] + Value[i]);
}
}
void Print_Res_Optimize()/*列印*/
{
cout << "The max value of Package:" << _Package[MAX_Volume] << endl;
}
-
多重揹包:
概念:
有Goods_Num件物品,MAX_Volume的最大裝載量,第i件物品有數量為Num[i],每種物品都有對應的重量或者說體積Volume[i],價值Value[i],求解裝包的最大價值
狀態轉移方程推導:
其實就是在一個展開的01揹包問題,比如有3件相同物品,不如將它展開成 1 1 1 形式,然後01裝包
狀態轉移方程:
a. Volume[ i ] > j :Package[ i-1 ][ j ]
b. j >= Volume[i] :max( Package[ i-1 ][ j-Volume[ i ] ] +Value[i], Package[ i-1 ][ j ] )
只不過,對於物品數大於1的,需要展開Num[i]次
實現程式碼:
#include<iostream>
#include<algorithm>
#include<memory>
using namespace std;
#define MAX_SIZE 1024
int Volume[MAX_SIZE]; /*每個物品所佔空間(或重量)*/
int Value[MAX_SIZE]; /*物品價值*/
int Num[MAX_SIZE]; /*i物品的個數*/
int Package[MAX_SIZE]; /*優化後,Package[i]表示容量i的購物車所獲得的最大價值*/
int MAX_Volume, Goods_Num; /*最大容量,物品數量*/
void Init()
{
memset(Package, 0, sizeof(Package));
}
void Multi_Package()
{
for (int i = 1; i <= Goods_Num; i++)
{
for (int j = 1; j <= Num[i]; j++)
{
for (int k = MAX_Volume; k >= Volume[i]; k--)
Package[k] = max(Package[k], Package[k - Volume[i]] + Value[i]);
}
}
}
void Print_Res()
{
cout << "The max value of Package:" << Package[MAX_Volume] << endl;
}
優化:
這時候想一個問題,如果一件物品的數量很大很大,那麼,對於第三個迴圈需要跑的時間就很長,在前面說過,多重揹包是可拆成01揹包形式,那麼,何不提前處理 Volume[MAX_SIZE],Value[MAX_SIZE],將問題先轉成01揹包。
轉化時有技巧,運用快速冪的知識,將一件物品拆成 1,2,4.......,當然有個容易想的前提:1~N的數可由2的若干指數和表示
優化程式碼:
#include<iostream>
#include<algorithm>
#include<memory.h>
using namespace std;
#define MAX_SIZE 1024
int Op_Vol[MAX_SIZE]; /*優化後的物品體積*/
int Op_Val[MAX_SIZE]; /*優化後的物品價值*/
int Package[MAX_SIZE]; /*揹包*/
int MAX_Volume,GoodsNum; /*揹包最大體積,原先的貨品數量*/
int Op_GoodsNum; /*優化後的物品數*/
void Init() /*初始化*/
{
Op_GoodsNum=0;
memset(Op_Val,0,sizeof(Op_Val));
memset(Op_Vol,0,sizeof(Op_Vol));
memset(Package,0,sizeof(Package));
}
void Multi_Package()
{
for(int i=1;i<=Op_GoodsNum;i++) /*優化後轉變為01揹包問題*/
{
for(int j=MAX_Volume;j>=Op_Vol[j];j--)
Package[j]=max(Package[j],Package[j-Op_Vol[i]]+Op_Val[i]);
}
}
int main()
{
int vol,val,num;
while(cin>>GoodsNum>>MAX_Volume)
{
Init();
for(int i=1;i<=GoodsNum;i++)
{
cin>>vol>>val>>num;
for(int j=1;j<=num;j<<=1) /*優化成01揹包問題*/
{
Op_Val[++Op_GoodsNum]=j*val;
Op_Vol[Op_GoodsNum]=j*vol;
num-=j;
}
if(num>0)
{
Op_Val[++Op_GoodsNum]=num*val;
Op_Vol[++Op_GoodsNum]=num*vol;
}
}
Multi_Package();
cout<<Package[MAX_Volume]<<endl;
}
return 0;
}