WOJ 1005

01揹包

前言
 揹包問題本身已經是老生常談了,但是這依舊是一個十分經典的問題,初學動態規劃的朋友們肯定都繞不過它。網上已經有了很多十分優秀的揹包問題講解,譬如經典的《揹包九講》,但是我自己學了揹包之後發現一個問題就是理解不夠透徹,見到一個問題學一次,學一次就能刷幾道題。現在我自己重新做了一些算法題,遇到了簡單的01揹包依舊不知道怎麼做,於是就憑藉零散的記憶和自我理解,對這些問題進行了求解,在這裏把我的理解寫下來,一方面如果能夠幫助到其他人也算是一件善事,另一方面也算是一種成長記錄。


正文
 01揹包的表述爲有n 個物品,它們有各自的重量和價值,現有給定容量的揹包,如何讓揹包裏裝入的物品具有最大的價值總和?
 此處假設第i個物品的花費爲c[i],價值爲v[i];則對於任一個物品對問題的總貢獻只能是0或者是v[i],即取或者不取;設在當前最大揹包容量爲V的情況下,考慮第i個物品的貢獻值時的最優解爲

f[i][V]=max{ f[ i-1 ][ V ] , f[ i-1 ][ V-c[ i ] ]+v[ i ]}

 式中f[i][V]表示i個物品在容量爲V的情況下的最優解。 有了動規方程,接下來就是確定平凡解。 容易知道當容量爲0時,不論物品的價值如何,最優解都是0,即f[i][0]恆等於0;另外當只有一個物品的時候,如果一個物品能放進揹包裏,那麼不論揹包容量有多大,最優解也只能爲這個物品的價值,即f[0][val]爲0或者v[0]。

看到這裏可能會有一點抽象,那麼我們來進行一個簡單的模擬,假如揹包容量V=4,c[0]=2,v[0]=2;c[1]=1,v[1]=1.
由上面的分析可以得到如下表格:

    i\v 0 1 2 3 4
0 0 0 3 3 3
1 0 1 3 4 4
由表格可以看到第0行第0列都是平凡解,只有第1行是需要求值的,f[1][1]時,取放入第1個物品和不放入第1個物品的最大值,即max{ f[0][1], f[0][1-c[1]]+v[1] },後面的值同理可以求出來。

效率分析
 時間複雜度上易知,需要遍歷整個表格,所以時間複雜度是O(n*V),V是揹包大小,n是物品個數。空間複雜度的上界是O(n*V)。一般來說當揹包容量達到10^5的時候空間複雜度就難以忍受了,但是時間上還有餘裕。那麼如何進行空間優化呢?
 如果通過上面的例子求解和動規方程,聰明的你就能發現,下一行的值只跟上一行的值有關,即如果我們的目標是計算最終結果而不需要知道中間值的話,那麼剩下的那些都是無用數據。所以我們只需要存儲兩行,計算的時候讓其滾動即可。
 下面附上一道whuoj的題,貼上代碼,註釋部分是樸素解。

#include <iostream>

using namespace std;

const int maxn = 102;
const int V=100005;

int c[maxn],v[maxn];

//int f[maxn][V];
int f1[V],f2[V];


int main(){
	int n,maxv;
	while(cin>>n){
		for(int val=0;val<V;val++){
			f1[val]=0;f2[val]=0;
		}
		for(int i=0;i<n;i++){
			cin>>c[i]>>v[i];
			if(i-c[i]>=0)
				f1[i]=v[i];
			else f1[i]=0;
		}
		cin>>maxv;
		f2[0]=0;
		for(int i=0;i<n;i++){
			for(int val=1;val<=maxv;val++){
				if(val-c[i]>=0&&(f1[val-c[i]]+v[i])>f1[val])
					f2[val]=f1[val-c[i]]+v[i];
				else f2[val]=f1[val];
			}
			for(int val=0;val<=maxv;val++)
				f1[val]=f2[val];
		}
		cout<<f2[maxv]<<endl;
		/*cin>>maxv;
		for(int val=0;val<=maxv;val++)
			if(val-c[0]>=0)
				f[0][val]=v[0];

		for(int i=1;i<n;i++)
			for(int val=1;val<=maxv;val++)
				if((val-c[i])>=0&&(f[i-1][val-c[i]]+v[i])>f[i-1][val])
					f[i][val]=f[i-1][val-c[i]]+v[i];
				else
					f[i][val]=f[i-1][val];

		for(int i=0;i<n;i++){
			for(int v=0;v<=maxv;v++)
				cout<<f[i][v]<<" ";
			cout<<endl;
		}
		cout<<f[n-1][maxv]<<endl;*/
	}
	return 0;
}

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