1. 程式人生 > 其它 >動態規劃簡單例子——國王與金礦(c++)

動態規劃簡單例子——國王與金礦(c++)

動態規劃的要點:確定全域性最優解和最優子結構之間的關係,以及問題的邊界。以數字的形式表達就是狀態轉移方程式。下面以一個例子來對他們進行描述。

問題描述:

有一個國家發現了5座金礦,每座金礦的黃金儲量不同,需要參與挖掘的工人人數也不同。參與挖礦工人的總數是10人。每座金礦要麼全挖,要麼不挖 ,不能派出一半人挖取一半金礦。要求如何分配工人才能挖出最多的黃金。

第一座金礦含金500,需要5人;第二座金礦含金200,需要3人;第三座金礦含金300,需要4人;第四座金礦含金350,需要3人;第五座金礦含金400,需要5人。

思路:

對於此類提到獲得最多的問題,首先想到的一定是做比較,得出在滿足人員條件的情況下,選擇出含金量最多的方案。

設w為工人總數,n為金礦數,p[]為金礦要的人,g[]為含金量.

將問題細小化,如果只有三座金礦,那麼比較方法一定是先看第一座金礦,滿足條件則記錄數值,此時的員工數減去第一座金礦所需人數,再對第二座金山進行比較,看是否滿足人數要求,比較挖和不挖的最大值作為當前的最優結果。以這種思路很容易可以寫一個遞迴的程式,如下:

int getmine(int w,int n,int p[],int g[])
{
	if(w==0||n==0) return 0;
	if(w<p[n-1])//比較當前金礦是否可以挖掘,不能則繼續下一座 
	{
		return getmine(w,n-1,p,g);
	}	
	return max(getmine(w,n-1,p,g),getmine(w-p[n-1],n-1,p,g)+g[n-1]);
//在挖與不挖之間作比較,選擇最大值 
}

但是這樣會導致很多重複計算的值,而且隨著數量的增加需要的空間也會越來越大。所以我們可以想到使用一張表來儲存結果。由上面的分析可以知道,我們可以將問題簡化為一個關於已經挖了4個金礦比較第5個金礦挖與不挖獲得含金量多少的比較。

可以直接貼出程式碼

int getmine_2(int w,int n,int p[],int g[])
{
	int a[N][M];
	for(int i = 1;i<=n;i++)
		a[i][0] = 0;
	int i = 1;
	for(int j = 1;j<=w;j++)
	{
		if(j<p[0]) a[i][j] = 0;
		else{
			a[i][j] = g[0];
		}
	} 
	for(int i = 2;i<=n;i++){
		for(int j = 1;j<=w;j++)
		{
			if(j<p[i-1]) {
				a[i][j] = a[i-1][j];
			}
			else{
				a[i][j] = max(a[i-1][j],a[i-1][j-p[i-1]]+g[i-1]);//在比較時,應該看的是人數一定,但不同金礦數而產生不同的收益值 
			}
		}
	
	}
	for(int i = 1;i<=n;i++){
		for(int j = 1;j<=w;j++)
			cout<<a[i][j]<<" ";
		cout<<endl;
	}
} 

上圖是輸出的記錄表數值的記錄。橫向為員工數,縱向為金礦數。

其實在實際求解時,不需要儲存這個二維陣列,只需要儲存最後一行,最後輸出陣列中最後一個數字即可,這樣是為了更好的看出比較的過程。

解決時遇到的問題

本題整體思路並不難,只是在人數小於要求時返回0,滿足時得到新結果進行一個比較並且記錄。

需要注意的是,在比較時應該是a[i-1][j]與a[i-1][j-p[i-1]]+g[i-1],而不是a[i][j-p[i-1]]+g[i-1].只有上層才是對應之前的狀態。以及初始化陣列時應該給a[i][0]賦初值0。因為如果人數剛好滿足j列會需要之前a[i-1][0]的值相加。

思路可能要點亂,看程式碼就好了,以後技術提高了再修改。