動態規劃簡單例子——國王與金礦(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]的值相加。
思路可能要點亂,看程式碼就好了,以後技術提高了再修改。