完全揹包

有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了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章