1. 程式人生 > >[dp]揹包問題( TSOJ 1306 ) —— 孤獨的餃子

[dp]揹包問題( TSOJ 1306 ) —— 孤獨的餃子

首先,我還是要先囉嗦一下揹包問題的題外話。

動態規劃(dp)是求解決策過程最優化的數學方法,其過程大概如下:為了尋求最優解,我們可以把多階段過程轉化為一系列單階段問題,利用各階段之間的關係,逐個求解。

咳咳,不囉嗦了,步入正題。

揹包問題最基本的分為三大類:0-1揹包,完全揹包和多重揹包。

我先稍微解釋一下三種揹包的大概意思。0-1揹包是指往揹包裡裝入的物品每種只有一件,完全揹包則是往揹包裡裝入的物品每種有無數件,顯然這兩種情況都過於理想化,而多重揹包則是一種更為貼近實際生活的揹包型別,即每種物品都為有限件,第i種物品的件數可記為amount[i]。

我們現在看一個例題,TSOJ的1306揹包問題。

題目描述:

有n種物品,每種均有無窮多個。第i種物品的體積為Vi,重量為Wi。選一些物品裝到一個體積容量為C的揹包中,使得揹包內物品在總體積不超過C的前提下重量儘量大。

輸入描述:

第一行有兩個整數n(1 <= n <= 100)和C(1 <= C <= 10000),用一個空格隔開,n表示貨物種類數,C代表揹包體積容量。接下來的n行每行包括兩個在1到10000之間(包括1和10000)的整數Vi,Wi,分別表示物品的體積和重量。

輸出描述:

包括一行,這一行只包含一個整數,表示在規定的揹包體積大小內,揹包內物品的最大重量

樣例輸入:

3 5

1 2

2 3

3 2

樣例輸出:

10

不難看出,這是一個典型的完全揹包問題。解題思路大概是這樣:1,開一個一維dp陣列用來記錄揹包容量從0 - C的動態情況。2,對每種物品依次更新揹包從(V[i]) - C的情況。3,直到對最後一種物品更新完資料,取出最大值,即為最終結果。

更新揹包的資料來源有兩個:(1)上一次dp當前容量的值。(2)上一次dp當前容量減去當前物品體積的值加上當前物品重量。

毋庸置疑,我們要用資料來源中較大的對dp進行更新,這樣就可以確保每次更新完後的dp值都是隻裝入前i個物品的當前容量的最優解。

你可能聽我囉嗦完還是一頭霧水,不用擔心,你可以先看下面的程式碼,看完後可能就會有所體悟。

#include<stdio.h>

int dp[10005];

int max(int a,int b)
{
	return a>b?a:b;
}

int main()
{
	int i,j,n,c,cc[105],ww[105];
	while(scanf("%d %d",&n,&c)!=EOF)
	{
		int maxf=0;
		for(i=0;i<=c;i++)	
			dp[i]=0;
		for(i=1;i<=n;i++)
			scanf("%d %d",&cc[i],&ww[i]);
		for(i=1;i<=n;i++)
			for(j=cc[i];j<=c;j++)
			{
				dp[j]=max ( dp[j] , dp[j-cc[i]]+ww[i] );
				maxf=dp[j];
			}	
		printf("%d\n",maxf);
	}
	return 0;
}

這是我用最基本的C語言編寫的原始碼,只使用了標準輸入輸出的標頭檔案,以便強化作為初學者的我的語言功底。餃子作為一隻小萌新,給廣大萌新小夥伴們提個建議,儘量多自己編寫程式碼,減少使用庫函式。( P.S.有時候還會降低智商的,開玩笑的啦。 )當然等語言功底紮實之後,你就可以為所欲為了,此處手動滑稽。

這時候我們注意到,當遇到第i種物品時,小於V[i]的dp值是不需要更新的,因為他們壓根就裝不下啊,當然這種說法也不太嚴謹,但是便於理解。此時我們可以從另一個角度理解,這些資料是更新了的,且更新完的資料還是原來的值,只不過聰明的你發現了這個規律,讓傻不拉幾的只會機械地執行的計算機減少了這些無效的操作,人是有思維的,這就是計算機和人類本質的區別鴨!

其實我應該從二維陣列dp[i][j]對完全揹包進行講解,然後再講空間優化問題,但是我感覺,直接學習空間優化完的揹包問題就足夠了,因為我感覺兩者在程式碼實現難度上沒什麼區別鴨,這種不多費力氣又省記憶體的方法學會了,豈不是事半功倍了鴨?

搞懂了完全揹包,我覺得0-1揹包和多重揹包也不是什麼問題了。0-1揹包和完全揹包的區別就是:

完全揹包dp的更新方向是:→→→→→→→→→→→→→→→→→→→→→

而0-1揹包的更新方向是:←←←←←←←←←←←←←←←←←←←←←

之所以完全揹包是從左到右,是因為左面的dp值是考慮了裝入若干個前i種物品後更新完的值,我們要用這些裝入了第i個物品後的dp值對後面繼續進行更新。而0-1揹包是用裝入前i-1種物品的dp值對當前要裝入第i個物品的dp值進行更新,為了確保每個數值都沒被第i個物品更新過,考慮完兩種資料來源後,我們發現從右到左這種更新方式巧妙地避免了這個問題,因為每次更新都是用左面的dp值對當前dp值進行更新,而若從左到右,左面的值是裝入了前i個物品後的dp值,不再是裝入前i-1個物品的dp值,這樣顯然就是錯誤的。

本餃子暫時就只想到了這麼多,可能講解中有一些錯誤沒有發現,還望大佬們多多指出。