[演算法] 二、國王和金礦

ajiang02發表於2020-02-15

題目

有一個國家發現了5座金礦,每座金礦的黃金儲量不同,需要參與挖掘的工人數也不同。參與挖礦工人的總數是10人。
每座金礦要麼全挖,要麼不挖,不能派出一半人挖取一半金礦。要求用程式求解出,要想得到儘可能多的黃金,應該選擇挖取哪幾座金礦?
工人:10名
金礦:400金/5人、500金/5人、200金/3人、300金/4人、350金/3人、

動態規劃問題建模階段

設金礦數量為 N, 工人數量為 W, 金礦黃金量為 G[], 金礦用工量為 P[]

工人數      | 1  2   3   4    5    6    7    8    9    10
——————————-+———————————————————————————————————————————————
第一座金礦  | 0  0   0    0   400  400  400  400  400  400
第二座金礦  | 0  0   0    0   500  500  500  500  500  900
第三座金礦  | 0  0  200  200  500  500  500  700  700  900
第四座金礦  | 0  0  200  300  500  500  500  700  800  900
第五座金礦  | 0  0  350  350  500  550  650  850  850  900

規律:
    3金礦8工人  = max( 2金礦8工人, 2金礦5工人+第三座金礦)  = max(500, 500+200)
    5金礦10工人 = max( 4金礦10工人, 4金礦7工人+第五座金礦) = max(900, 500+350)
    ……
    由此可見,只需要儲存前一行的結果,就可以推匯出新的一行。

動態規劃三個重要的概念

最優子結構:
由於第五座金礦可挖可不挖,所以10人5座金礦的最優子結構有兩種(選最大值):
10人4座金礦的挖金總量 和 前四座金礦的挖金總量+第五座金礦的挖金總量
= Max( F(4,10), F( 4,10-P[4] ) + G[4] )
邊界:
只有一座金礦時,如果工人夠,那就是金礦的黃金量;如果工人不夠,挖金總量就是0
當 N = 1, W >= P[0]時, F(N,W) = G[0];
當 N = 1, W < P[0]時, F(N,W) = 0
轉態轉移公式:
F(N,W) = 0 ,( N <= 1, W < P[0] ) 小於等於一座金礦,工人人數不夠開挖
F(N,W) = G[0] ,( N == 1, W >= P[0] ) 只有一座,工人人數夠開挖
F(N,W) = F(N-1, W) ,( N > 1, W < P[n-1] ) 最後一座金礦不挖
F(N,W) = max( F(N-1, W), F(N-1, W-P[N-1]) + G[N-1] ) ,( N > 1, W >= P[N-1] ) 取最後一座金礦挖與不挖的最大值

動態規劃求解問題階段

class Index extends TestCase
{
    /**
     * 動態規劃(Dynamic Programming)
     * 時間複雜度:O(N * W)
     * 空間複雜度:O(W)
     */
    public function test()
    {
        $N = 5;                                // 金礦數量
        $W = 10;                               // 工人數量
        $P = array(5, 5, 3, 4, 3);             // 每座金礦需要的用工數
        $G = array(400, 500, 200, 300, 350);   // 每座金礦的金礦重量
        $pre = $res = array();                 // 儲存前一行結果、最終結果

        // 只有一座金礦時的情況
        for ($i = 0; $i <= $W; $i++) {
            if ($i < $P[0]) {      // 如果當前迴圈工人數 i < 第一座金礦所需工人數 P[0],則為 0
                $pre[$i] = 0;
            } else {               // 否則就是第一座金礦的重量
                $pre[$i] = $G[0];
            }
        }

        // 迴圈金礦,即迭代每一層
        for ($i = 0; $i < $N; $i++) {
            // 迴圈工人數
            for ($j = 0; $j <= $W; $j++) {
                if ($j < $P[$i]) {           // 如果工人數不夠挖最後一座金礦
                    $res[$j] = $pre[$j];
                } else {                     // 如果工人數夠挖最後一座
                    $res[$j] = max($pre[$j], $pre[$j - $P[$i]] + $G[$i]);
                }
            }
            $pre = $res;
        }
        halt($res[$N]);
    }
}

參考:程式設計師小灰

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章