有n個物品,每個物品體積是costs = {c1,c2,...cn},每個物品的價值是values = {v1,v2,...vn},每個物品只能取無限次。現在有體積爲v的揹包,問將這些物品放入該揹包,能得到的最大價值是多少?並輸出最大時的選擇方案。
推導:設dp[i][j]爲將前i個物品放入體積爲j的揹包時取得的最大價值。來看,dp[i][j]可由哪些狀態得到?
1、若第i個物品不放入揹包,那麼dp[i][j] = dp[i - 1][j];
2、若第i個物品放入揹包,那麼此時dp[i][j]不能再像01揹包那樣子等於dp[i - 1][j - costs[i]] + values[i]了,因爲dp[i - 1][k]前i - 1個物品,可現在的要求是每個物品都可以放無數次。那麼,就應該是dp[i][j] = dp[i][j - costs[i]] + values[i],表示dp[i][j]是由當前i狀態得來的而不是由它的前一個狀態i - 1得來的。
那麼完全揹包的狀態方程就應該是:dp[i][j] = max(dp[i - 1][j],dp[i][j - costs[i]] + values[i])(i = 0 或者j = 0時dp[i][j] = 0),由此可寫出代碼:
for(int i = 1;i <= n;i++)
for(int j = 0;j <= v;v++)
if(j < costs[i])
dp[i][j] = dp[i - 1][j];
else
dp[i][j] = max(dp[i - 1][j],dp[i][j - costs[i]] + values[i]);
由此可看出完全揹包的時間複雜度爲O(nv),空間複雜度爲O(nv)。同01揹包一樣,我們同樣可以用滾動數組將其空間複雜度降爲O(v)。
for(int i = 1;i <= n;i++)
for(int j = costs[i];j <= v;j++)
dp[j] = max(dp[j],dp[j - costs[i]] + values[i]);
可以看到,區01揹包的區別只是內層循環變爲順序了。關於利用滾動數組來降低空間複雜度,只要在紙上畫下就清楚了。我們再來看如何記錄路徑。我們用path[i][j] = (x,y)表示從(x,y)到(i,j)有條路徑,那麼i != x的時候說明第i個物品沒有放進揹包。
#define N 1001
struct CompletePack
{
/*
*numOfres:物品的個數
*volumeOfpack:揹包容量
*path:記錄路徑
*costs[i]:第i個物品的消耗
*values[i]:第i個物品的價值
*求放進容量爲volumeOfpack的揹包能取得的最大價值
*/
int dp[N];
int path[N][N][2];
int costs[N],values[N],numOfres,volumeOfpack;
CompletePack(int numOfres,int volumeOfpack,int costs[],int values[])
{
this->numOfres = numOfres;
this->volumeOfpack = volumeOfpack;
for(int i = 1;i <= this->numOfres;i++)
this->costs[i] = costs[i],this->values[i] = values[i];
memset(dp,0,sizeof(dp));
memset(path,0,sizeof(path));
}
int getMaxValue()
{
for(int i = 1;i <= numOfres;i++)
{
int j;
for(j = 0;j < costs[i];j++)
path[i][j][0] = i - 1,path[i][j][1] = j;
for(;j <= volumeOfpack;j++)
{
int tmp = dp[j - costs[i]] + values[i];
if(dp[j] < tmp)
{
dp[j] = tmp;
path[i][j][0] = i,path[i][j][1] = j - costs[i];
}
else
path[i][j][0] = i - 1,path[i][j][1] = j;
}
}
return dp[volumeOfpack];
}
/*
* 輸出路徑,path[i][j] = (x,y),表示從(x,y)到(i,j)有一條路徑,當path[i][j] = (i - 1,j)時表示第i個物品沒有放入揹包
* 初始參數爲(numOfres,volumeOfpack)
*/
void getPath(int i,int j)
{
if(!i || !j)
return;
if(path[i][j][0] == i - 1)
{
getPath(i - 1,j);
return;
}
int num = 0;//記錄第i個物品放入揹包的個數
while(path[i][j][0] == i)
{
num++;
j = path[i][j][1];
}
getPath(i,j);
//values[i]*num
printf("values[%d]:%d*%d ",i,values[i],num);
}
};
題目:
1、hdu 1248 模板題
2、hdu 1114 求剛好能放滿v的揹包時的最小价值。初始化時dp[0] = 0,其它爲INF,表示不合法。
3、hdu 1284 完全揹包求排列組合的總方案數。設dp[i][j]爲前i個物品能組成容量j的方案數,那麼dp[i][j] = dp[i - 1][j] + dp[i][j - costs[i]]。
4、poj 3181 同1284一樣,也是求方案數。不過這題結果超過了long long,要用到大數相加。
5、hdu 2159 二維完全揹包。用dp[i][j]表示用i的忍耐值和殺j只怪能得到的最大經驗值。因爲題目要求求能升級的消耗最少的忍耐值的方案,故最後遍歷dp數組找個最優值就OK了。