動態規劃系列(1)——金礦模型的理解

參考:http://www.cnblogs.com/sdjl/articles/1274312.html

本文主要總結了引文中提出的金礦模型的思考方法,并用python代碼來實現算法,從而加深了對動態規劃思想的理解。

1. 故事描述

注:內容節選自開篇引文。

有一個國家,所有的國民都非常老實憨厚,某天他們在自己的國家發現了十座金礦,并且這十座金礦在地圖上排成一條直線,國王知道這個消息后非常高興,他希望能夠把這些金子都挖出來造福國民,首先他把這些金礦按照在地圖上的位置從西至東進行編號,依次為0、1、2、3、4、5、6、7、8、9,然后他命令他的手下去對每一座金礦進行勘測,以便知道挖取每一座金礦需要多少人力以及每座金礦能夠挖出多少金子,然后動員國民都來挖金子。

  • 題目補充1:挖每一座金礦需要的人數是固定的,多一個人少一個人都不行。國王知道每個金礦各需要多少人手,金礦i需要的人數為peopleNeeded[i]
  • 題目補充2:每一座金礦所挖出來的金子數是固定的,當第i座金礦有peopleNeeded[i]人去挖的話,就一定能恰好挖出gold[i]個金子。否則一個金子都挖不出來。
  • 題目補充3:開采一座金礦的人完成開采工作后,他們不會再次去開采其它金礦,因此一個人最多只能使用一次。
  • 題目補充4:國王在全國范圍內僅招募到了10000名愿意為了國家去挖金子的人,因此這些人可能不夠把所有的金子都挖出來,但是國王希望挖到的金子越多越好。
  • 題目補充5:這個國家的每一個人都很老實(包括國王),不會私吞任何金子,也不會弄虛作假,不會說謊話。
  • 題目補充6:有很多人拿到這個題后的第一反應就是對每一個金礦求出平均每個人能挖出多少金子,然后從高到低進行選擇,這里要強調這種方法是錯的,如果你也是這樣想的,請考慮背包模型,當有一個背包的容量為10,共有3個物品,體積分別是3、3、5,價值分別是6、6、9,那么你的方法取到的是前兩個物品,總價值是12,但明顯最大值是后兩個物品組成的15。
  • 題目補充7:我們只需要知道最多可以挖出多少金子即可,而不用關心哪些金礦挖哪些金礦不挖。

那么,國王究竟如何知道在只有10000個人的情況下最多能挖出多少金子呢?

2. 問題抽象

注:內容節選自開篇引文。

輸入文件名為gold.in

輸入文件第一行有兩個數M和N,M是國王可用用來開采金礦的總人數,N是總共發現的金礦數。

輸入文件的第2至N+1行每行有兩個數,第i行的兩個數分別表示第i-1個金礦需要的人數和可以得到的金子數。

輸出文件僅一個整數,表示能夠得到的最大金子數。

輸入樣例:

100 5

77 92

22 22

29 87

50 46

99 90

輸出樣例:

133

3. 分析

按照原博客中的思路進行分析,定義如下幾個數組和函數:

  • needed_people_num[i]:表示開采金礦i(注意:i從0開始,0 <= i < N,下同)所需的人數。
  • gold_num[i]:表示開采金礦i能獲取到的金子數。
  • f(people_num,mine_num):表示當用people_num個人挖第0~mine_num座金礦可以獲取到的最大金子數。

狀態轉移方程如下:
注:其中將people_num簡記為:pnmine_num簡記為:mnneeded_people_num[]簡記為:npn[]gold_num簡記為:gn

分析:

  • mine_num = 0是邊界條件,也是遞歸的結束條件。
  • 因為有很多次遞歸會重復計算,所以考慮采用一個二維緩存數組(備忘錄)來存儲每次計算的結果值。

4. Talk is cheap, show you the code

# coding:utf-8

# 可支配的總人數
total_people_num = 0
# 金礦總數
total_mine_num = 0
# 開采每個金礦所需的人數列表
needed_people_num = []
# 開采每個金礦可以獲取到的金子數列表
gold_num = []
# 緩存數組,是一個二維數組,有total_people_num+1行,total_mine_num+1列
# 使用緩存數組是為了減少重復計算,cache[i][j]表示用i個人開采第0~j座金礦一共能開采到的金子總數最大值
# cache的所有元素都在一開始被初始化為-1,表示未知
# cache的使用可以極大地提高效率,減少很多重復計算
cache = []

# 初始化數據
def init():
    global total_people_num
    global total_mine_num
    global needed_people_num
    global gold_num
    global cache
    
    # 從gold.in文件中讀取數據
    '''
    文件內容:
    100 5
    77 92
    22 22
    29 87
    50 46
    99 90
    '''
    datas = []
    with open('gold.in','r') as f_in:
        datas = f_in.readlines()
        
    # 解析出總人數和金礦數
    line1 = datas[0]
    total_people_num = int(line1.split()[0])
    total_mine_num = int(line1.split()[1])
    
    # 解析出needed_people_num和gold_num列表
    for line in datas[1:]:
        needed_people_num.append(int(line.split()[0]))
        gold_num.append(int(line.split()[1]))
        
    # 初始化cache數組,下面這種方式是有問題的:
    # cache_row = [-1] * (total_mine_num + 1)
    # cache = [cache_row] * (total_people_num + 1)
    # 要用這種方式(為了防止數組越界,都加了1):
    cache = [([-1] * (total_mine_num + 1)) for i in range(total_people_num + 1)]
    
# 求最大金子數的函數
# get_max_gold_num(i,j)表示用i個人開采第0~j座金礦可以開采到的最大金子總數
def get_max_gold_num(people_n,mine_n):
    max_num = 0
    
    # 1. 如果緩存數組中有對應值,直接從中取
    if cache[people_n][mine_n] != -1:
        max_num = cache[people_n][mine_n]
    # 2. 如果只開采第0座金礦
    elif mine_n == 0:
        # 2.1 如果人數小于開采第0座金礦所需人數,那么結果就是0
        if people_n < needed_people_num[mine_n]:
            max_num = 0
        # 2.2 否則最終結果就是開采第0座金礦所能獲取到的金子數     
        else:
            max_num = gold_num[mine_n]
    # 3. 如果不是第0座金礦且人數足夠開采第mine_n座金礦,那么取下面兩種開采策略所能獲取到的最大金子數的較大值
    elif people_n >= needed_people_num[mine_n]:
        # 用people_n個人去開采第0~mine_n - 1座金礦所能獲取到的最大金子數
        m = get_max_gold_num(people_n,mine_n - 1)
        # 用people_n個人去開采第0~mine_n座金礦所能獲取到的金子數的最大值
        n = gold_num[mine_n] + get_max_gold_num(people_n - needed_people_num[mine_n],mine_n - 1)
        max_num = max(m,n)
    # 4. 如果不是第0座金礦且人數不夠開采第mine_n座金礦,那只能采取第一種策略了:使用people_n個人開采其他的mine_n - 1座金礦
    else:
        max_num = get_max_gold_num(people_n,mine_n - 1)
        
    # 5. 給緩存數組對應元素賦值
    cache[people_n][mine_n] = max_num
    return max_num
            
def main():
    init()
    print get_max_gold_num(total_people_num,total_mine_num - 1)
    
main()

輸出結果:

133
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容