今天在網上看到一個講動態規劃的文章,是以01揹包爲例的,這文章和書上的講解很是不同,令我眼前一亮,因而轉載一下下~~~
(說明一下,本人很是痛恨教材公式定理漫天飛,實際的講解卻講得很是枯澀難懂,這種中國式的教育已經延綿了幾千年了,如今中國的教材仍是這個樣子,講清楚些明白些就那麼難麼?高中有個老師講的一句話一直以爲頗有道理:「教得會天才不是真本事,能把博士生的東西講到小學生都會用那纔是真水平。」)
附上原文地址:
http://www.cnblogs.com/sdjl/articles/1274312.html
經過金礦模型介紹動態規劃
對於動態規劃,每一個剛接觸的人都須要一段時間來理解,特別是第一次接觸的時候老是想不通爲何這種方法可行,這篇文章就是爲了幫助你們理解動態規劃,並經過講解基本的01揹包問題來引導讀者如何去思考動態規劃。本文力求通俗易懂,無異性,不讓讀者感到迷惑,引導讀者去思考,因此若是你在閱讀中發現有不通順的地方,讓你產生錯誤理解的地方,讓你可貴讀懂的地方,請跟貼指出,謝謝!
----第一節----初識動態規劃--------
經典的01揹包問題是這樣的:
有一個包和n個物品,包的容量爲m,每一個物品都有各自的體積和價值,問當從這n個物品中選擇多個物品放在包裏而物品體積總數不超過包的容量m時,可以獲得的最大價值是多少?[對於每一個物品不能夠取屢次,最多隻能取一次,之因此叫作01揹包,0表示不取,1表示取]
爲了用一種生動又更形象的方式來說解此題,我把此題用另外一種方式來描述,以下:
有一個國家,全部的國民都很是老實憨厚,某天他們在本身的國家發現了十座金礦,而且這十座金礦在地圖上排成一條直線,國王知道這個消息後很是高興,他但願可以把這些金子都挖出來造福國民,首先他把這些金礦按照在地圖上的位置從西至東進行編號,依次爲0、一、二、三、四、五、六、七、八、9,而後他命令他的手下去對每一座金礦進行勘測,以便知道挖取每一座金礦須要多少人力以及每座金礦可以挖出多少金子,而後動員國民都來挖金子。
題目補充1:挖每一座金礦須要的人數是固定的,多一我的少一我的都不行。國王知道每一個金礦各須要多少人手,金礦i須要的人數爲peopleNeeded。
題目補充2:每一座金礦所挖出來的金子數是固定的,當第i座金礦有peopleNeeded人去挖的話,就必定能剛好挖出gold個金子。不然一個金子都挖不出來。
題目補充3:開採一座金礦的人完成開採工做後,他們不會再次去開採其它金礦,所以一我的最多隻能使用一次。
題目補充4:國王在全國範圍內僅招募到了10000名願意爲了國家去挖金子的人,所以這些人可能不夠把全部的金子都挖出來,可是國王但願挖到的金子越多越好。
題目補充5:這個國家的每個人都很老實(包括國王),不會私吞任何金子,也不會弄虛做假,不會說謊言。
題目補充6:有不少人拿到這個題後的第一反應就是對每個金礦求出平均每一個人能挖出多少金子,而後從高到低進行選擇,這裏要強調這種方法是錯的,若是你也是這樣想的,請考慮揹包模型,當有一個揹包的容量爲10,共有3個物品,體積分別是三、三、5,價值分別是六、六、9,那麼你的方法取到的是前兩個物品,總價值是12,但明顯最大值是後兩個物品組成的15。
題目補充7:咱們只須要知道最多能夠挖出多少金子便可,而不用關心哪些金礦挖哪些金礦不挖。
那麼,國王究竟如何知道在只有10000我的的狀況下最多能挖出多少金子呢?國王是如何思考這個問題的呢?
國王首先來到了第9個金礦的所在地(注意,第9個就是最後一個,由於是從0開始編號的,最西邊的那個金礦是第0個),他的臣子告訴他,若是要挖取第9個金礦的話就須要1500我的,而且第9個金礦能夠挖出8888個金子。聽到這裏國王哈哈大笑起來,由於原先他覺得要知道十個金礦在僅有10000我的的狀況下最多能挖出多少金子是一件很難思考的問題,可是,就在剛纔聽完他的臣子所說的那句話時,國王已經知道總共最多能挖出多少金子了,國王是如何在不瞭解其它金礦的狀況下知道最多能挖出多少金子的呢?他的臣子們也不知道這個謎,所以他的臣子們就問他了:「最聰明的國王陛下,咱們都沒有告訴您其它金礦的狀況,您是如何知道最終答案的呢?」
得意的國王笑了笑,而後把他最得意的「左、右手」叫到跟前,說到:「我並不須要考慮最終要挖哪些金礦才能獲得最多的金子,我只須要考慮我面前的這座金礦就能夠了,對於我面前的這座金礦不外乎僅有兩種選擇,要麼挖,要麼不挖,對吧?」
「固然,固然」大臣們回答倒。
國王繼續說道:「若是我挖取第9座金礦的話那麼我如今就能得到8888個金子,而我將用去1500我的,那麼我還剩下8500我的。我親愛的左部下,若是你告訴我當我把全部剩下的8500我的和全部剩下的其它金礦都交給你去開採你最多能給我挖出多少金子的話,那麼我不就知道了在第9個金礦必定開採的狀況下所能獲得的最大金幣數嗎?」
國王的左部下聽後回答道:「國王陛下,您的意思是若是我能用8500我的在其它金礦最多開採出x個金幣的話,那您一共就可以得到 x + 8888個金子,對嗎?」
「是啊,是啊……若是第9座金礦必定開採的話……」大臣們點頭說到。
國王笑着繼續對着他的右部下說到:「親愛的右部下,也許我並不打算開採這第9座金礦,那麼我依然擁有10000我的,若是我把這10000我的和剩下的金礦都給你的話,你最多能給我挖出多少個金子呢?」
國王的右部下聰明地說道:「尊敬的國王陛下,我明白您的意思了,若是我回答最多能購開採出y個金幣的話,那您就能夠在y和x+8888之間選擇一個較大者,而這個較大者就是最終咱們能得到的最大金幣數,您看我這樣理解對嗎?」
國王笑得更燦爛了,問他的左部下:「那麼親愛的左部下,我給你8500我的和其他金礦的話你能告訴我最多能挖出多少金子嗎?」
「請您放心,這個問題難不倒我」。左部下向國王打包票說到。
國王高興地繼續問他的右部下:「那右部下你呢,若是我給你10000我的和其他金礦的話你能告訴我最多能挖出多少金子嗎?」
「固然能了!交給我吧!」右部下同左部下同樣自信地回答道。
「那就拜託給大家兩位了,如今我要回到我那溫馨的王宮裏去享受了,我期待着大家的答覆。」國王說完就開始動身回去等消息了,他是多麼地相信他的兩個大臣可以給他一個準確的答覆,由於國王其實知道他的兩位大臣要比他聰明得多。
故事發展到這裏,你是否在想國王的這兩個大臣又是如何找到讓國王滿意的答案的呢?他們爲何可以如此自信呢?事實上他們的確比國王要聰明一些,由於他們從國王的身上學到了一點,就是這一點讓他們充滿了自信。
國王走後,國王的左、右部下來到了第8座金礦,早已在那裏等待他們的金礦勘測兵向兩位大臣報道:「聰明的兩位大臣,您們好,第8座金礦須要1000我的才能開採,能夠得到7000個金子」。
由於國王僅給他的左部下8500我的,因此國王的左部下叫來了兩我的,對着其中一我的問到:「若是我給你7500我的和除了第八、第9的其它全部金礦的話,你能告訴我你最多能挖出多少金子嗎?」
而後國王的左部下繼續問另外一我的:「若是我給你8500我的和除了第八、第9的其它全部金礦的話,你能告訴我你最多能挖出多少金子嗎?」
國王的左部下在內心想着:「若是他們倆都能回答個人問題的話,那國王交給個人問題不就解決了嗎?哈哈哈!」
由於國王給了他的右部下10000我的,因此國王的右部下一樣也叫來了兩我的,對着其中一我的問:「若是我給你9000我的和除了第八、第9的其它全部金礦的話,你能告訴我你最多能挖出多少金子嗎?」
而後國王的右部下繼續問他叫來的另外一我的:「若是我給你10000我的和除了第八、第9的其它全部金礦的話,你能告訴我你最多能挖出多少金子嗎?」
此時,國王的右部下同左部下同樣,他們都在爲本身如此聰明而感到知足。
固然,這四個被叫來的人一樣自信地回答沒有問題,由於他們一樣地從這兩個大臣身上學到了相同的一點,而兩位自認爲本身同樣很聰明的大臣得意地笑着回到了他們的府邸,等着別人回答他們提出來的問題,如今你知道了這兩個大臣是如何解決國王交待給他們的問題了嗎?
那麼你認爲被大臣叫去的那四我的又是怎麼完成大臣交給他們的問題的呢?答案固然是他們找到了另外八我的!
沒用多少功夫,這個問題已經在全國傳開了,更多人的人找到了更更多的人來解決這個問題,而有些人卻不須要去另外找兩我的幫他,哪些人不須要別人的幫助就能夠回答他們的問題呢?
很明顯,當被問到給你z我的和僅有第0座金礦時最多能挖出多少金子時,就不須要別人的幫助,由於你知道,若是z大於等於挖取第0座金礦所須要的人數的話,那麼挖出來的最多金子數就是第0座金礦可以挖出來的金子數,若是這z我的不夠開採第0座金礦,那麼能挖出來的最多金子數就是0,由於這惟一的金礦不夠人力去開採。讓咱們爲這些不須要別人的幫助就能夠準確地得出答案的人們鼓掌吧,這就是傳說中的底層勞動人民!
故事講到這裏先暫停一下,咱們如今從新來分析一下這個故事,讓咱們對動態規劃有個理性認識。
子問題:
國王須要根據兩個大臣的答案以及第9座金礦的信息才能判斷出最多可以開採出多少金子。爲了解決本身面臨的問題,他須要給別人製造另外兩個問題,這兩個問題就是子問題。
思考動態規劃的第一點----最優子結構:
國王相信,只要他的兩個大臣可以回答出正確的答案(對於考慮可以開採出的金子數,最多的也就是最優的同時也就是正確的),再加上他的聰明的判斷就必定能獲得最終的正確答案。咱們把這種子問題最優時母問題經過優化選擇後必定最優的狀況叫作「最優子結構」。
思考動態規劃的第二點----子問題重疊:
實際上國王也好,大臣也好,全部人面對的都是一樣的問題,即給你必定數量的人,給你必定數量的金礦,讓你求出可以開採出來的最多金子數。咱們把這種母問題與子問題本質上是同一個問題的狀況稱爲「子問題重疊」。然而問題中出現的不一樣點每每就是被子問題之間傳遞的參數,好比這裏的人數和金礦數。
思考動態規劃的第三點----邊界:
想一想若是不存在前面咱們提到的那些底層勞動者的話這個問題能解決嗎?永遠都不可能!咱們把這種子問題在必定時候就再也不須要提出子子問題的狀況叫作邊界,沒有邊界就會出現死循環。
思考動態規劃的第四點----子問題獨立:
要知道,當國王的兩個大臣在思考他們本身的問題時他們是不會關心對方是如何計算怎樣開採金礦的,由於他們知道,國王只會選擇兩我的中的一個做爲最後方案,另外一我的的方案並不會獲得實施,所以一我的的決定對另外一我的的決定是沒有影響的。咱們把這種一個母問題在對子問題選擇時,當前被選擇的子問題兩兩互不影響的狀況叫作「子問題獨立」。
這就是動態規劃,具備「最優子結構」、「子問題重疊」、「邊界」和「子問題獨立」,當你發現你正在思考的問題具有這四個性質的話,那麼恭喜你,你基本上已經找到了動態規劃的方法。
有了上面的這幾點,咱們就能夠寫出動態規劃的轉移方程式,如今咱們來寫出對應這個問題的方程式,若是用gold[mineNum]表示第mineNum個金礦可以挖出的金子數,用peopleNeeded[mineNum]表示挖第mineNum個金礦須要的人數,用函數f(people,mineNum)表示當有people我的和編號爲0、一、二、三、……、mineNum的金礦時可以獲得的最大金子數的話,f(people,mineNum)等於什麼呢?或者說f(people,mineNum)的轉移方程是怎樣的呢?
答案是:
當mineNum = 0且people >= peopleNeeded[mineNum]時 f(people,mineNum) = gold[mineNum]
當mineNum = 0且people < peopleNeeded[mineNum]時 f(people,mineNum) = 0
當mineNum != 0時 f(people,mineNum) = f(people-peopleNeeded[mineNum], mineNum-1) + gold[mineNum]與f(people, mineNum-1)中的較大者,前兩個式子對應動態規劃的「邊界」,後一個式子對應動態規劃的「最優子結構」請讀者弄明白後再繼續往下看。
----第二節----動態規劃的優勢--------
如今我假設讀者你已經搞清楚了爲何動態規劃是正確的方法,可是咱們爲何須要使用動態規劃呢?請先繼續欣賞這個故事:
國王得知他的兩個手下使用了和他相同的方法去解決交代給他們的問題後,不但沒有認爲他的兩個大臣在偷懶,反而很高興,由於他知道,他的大臣必然會找更多的人一塊兒解決這個問題,而更多的人會找更更多的人,這樣他這個聰明的方法就會在不經意間流傳開來,而全國人民都會知道這個聰明的方法是他們偉大的國王想出來的,你說國王能不高興嗎?
可是國王也有一些擔心,由於他實在不知道這個「工程」要動用到多少人來完成,若是幫助他解決這個問題的人太多的話那麼就太勞民傷財了。「會不會影響到今年的收成呢?」國王在內心想着這個問題,因而他請來了整個國家裏惟一的兩個數學天才,一個叫作小天,另外一個叫作小才。
國王問小天:「小天啊,我發覺這個問題有點嚴重,我知道其實這能夠簡單的當作一個組合問題,也就是從十個金礦中選取若干個金礦進行開採,看看哪一種組合獲得的金子最多,也許用組合方法會更好一些。你能告訴我一共有多少種組合狀況嗎?」
「國王陛下,若是用組合方法的話一共要考慮2的10次方種狀況,也就是1024種狀況。」小天思考了一會回答到。
「嗯……,若是每一種狀況我交給一我的去計算能獲得的金子數的話,那我也要1024我的,其實仍是挺多的。」國王好像再次感受到了本身的方法是正確的。
國王心理期待着小纔可以給它一個更好的答案,問到:「小才啊,那麼你能告訴我用個人那個方法總共須要多少人嗎?其實,我也計算過,好像須要的人數是1+2+4+8+16+32+64+……,畢竟每個人的確都須要找另外兩我的來幫助他們……」
不辜負國王的期待,小才微笑着說到:「親愛的國王陛下,其實咱們並不須要那麼多人,由於有不少問題實際上是相同的,而咱們只須要爲每個不一樣的問題使用一我的力即可。」
國王高興的問到:「此話如何講?」
「打個比方,若是有一我的須要知道1000我的和3個金礦能夠開採出多少金子,同時另外一我的也須要知道1000我的和3個金礦能夠開採出多少金子的話,那麼他們能夠去詢問相同的一我的,而不用各自找不一樣的人浪費人力了。」
國王思考着說到:「嗯,頗有道理,若是問題是同樣的話那麼就不須要去詢問兩個不一樣的人了,也就是說一個不一樣的問題僅須要一我的力,那麼一共有多少個不一樣的問題呢?」
「由於每一個問題的人數能夠從0取到10000,而金礦數能夠從0取到10,因此最多大約有10000 * 10 等於100000個不一樣的問題。」 小才一邊算着一邊回答。
「什麼?十萬個問題?十萬我的力?」國王有點失望。
「請國王放心,事實上咱們須要的人力遠遠小於這個數的,由於不是每個問題都會遇到,也許咱們僅須要1、兩百我的力就能夠解決這個問題了,這主要和各個金礦所須要的人數有關。」 小才馬上回答到。
故事的最後,天然是國王再一次向他的臣民們證實了他是這個國家裏最聰明的人,如今咱們經過故事的第二部分來考慮動態規劃的另外兩個思考點。
思考動態規劃的第五點----作備忘錄:
正如上面所說的同樣,當咱們遇到相同的問題時,咱們能夠問同一我的。講的通俗一點就是,咱們能夠把問題的解放在一個變量中,若是再次遇到這個問題就直接從變量中得到答案,所以每個問題僅會計算一遍,若是不作備忘的話,動態規劃就沒有任何優點可言了。
思考動態規劃的第六點----時間分析:
正如上面所說,若是咱們用窮舉的方法,至少須要2^n個常數時間,由於總共有2^n種狀況須要考慮,若是在揹包問題中,包的容量爲1000,物品數爲100,那麼須要考慮2^100種狀況,這個數大約爲10的30次方。
而若是用動態規劃,最多大概只有1000*100 = 100000個不一樣的問題,這和10的30次方比起來優點是很明顯的。而實際狀況並不會出現那麼多不一樣的問題,好比在金礦模型中,若是全部的金礦所需人口都是1000我的,那麼問題總數大約只有100個。
非正式地,咱們能夠很容易獲得動態規劃所需時間,若是共有questionCount個相同的子問題,而每個問題須要面對chooseCount種選擇時,咱們所需時間就爲questionCount * chooseCount個常數。在金礦模型中,子問題最多有大概people * n 個(其中people是用於開採金礦的總人數,n是金礦的總數),所以questionCount = people * n,而就像國王須要考慮是採用左部下的結果仍是採用右部下的結果同樣,每一個問題面對兩個選擇,所以chooseCount = 2,因此程序運行時間爲 T = O(questionCount * chooseCount) =O(people * n),別忘了實際上須要的時間小於這個值,根據所遇到的具體狀況有所不一樣。
這就是動態規劃的魔力,它減小了大量的計算,所以咱們須要動態規劃!
----第三節----動態規劃的思考角度----------
那麼什麼是動態規劃呢?我我的以爲,若是一個解決問題的方法知足上面六個思考點中的前四個,那麼這個方法就屬於動態規劃。而在思考動態規劃方法時,後兩點一樣也是須要考慮的。
面對問題要尋找動態規劃的方法,首先要清楚一點,動態規劃不是算法,它是一種方法,它是在一件事情發生的過程當中尋找最優值的方法,所以,咱們須要對這件事情所發生的過程進行考慮。而一般咱們從過程的最後一步開始考慮,而不是先考慮過程的開始。
打個比方,上面的挖金礦問題,咱們能夠認爲整個開採過程是從西至東進行開採的(也就是從第0座開始),那麼總有面對最後一座金礦的時候(第9座),對這座金礦不外乎兩個選擇,開採與不開採,在最後一步肯定時再去肯定倒數第二步,直到考慮第0座金礦(過程的開始)。
而過程的開始,也就是考慮的最後一步,就是邊界。
所以在遇到一個問題想用動態規劃的方法去解決時,不妨先思考一下這個過程是怎樣的,而後考慮過程的最後一步是如何選擇的,一般咱們須要本身去構造一個過程,好比後面的練習。
----第四節----總結-------
那麼遇到問題如何用動態規劃去解決呢?根據上面的分析咱們能夠按照下面的步驟去考慮:
一、構造問題所對應的過程。
二、思考過程的最後一個步驟,看看有哪些選擇狀況。
三、找到最後一步的子問題,確保符合「子問題重疊」,把子問題中不相同的地方設置爲參數。
四、使得子問題符合「最優子結構」。
五、找到邊界,考慮邊界的各類處理方式。
六、確保知足「子問題獨立」,通常而言,若是咱們是在多個子問題中選擇一個做爲實施方案,而不會同時實施多個方案,那麼子問題就是獨立的。
七、考慮如何作備忘錄。
八、分析所需時間是否知足要求。
九、寫出轉移方程式。
----第五節----練習-------
題目一:買書
有一書店引進了一套書,共有3卷,每卷書訂價是60元,書店爲了搞促銷,推出一個活動,活動以下:
若是單獨購買其中一卷,那麼能夠打9.5折。
若是同時購買兩卷不一樣的,那麼能夠打9折。
若是同時購買三卷不一樣的,那麼能夠打8.5折。
若是小明但願購買第1卷x本,第2卷y本,第3卷z本,那麼至少須要多少錢呢?(x、y、z爲三個已知整數)。
固然,這道題徹底能夠不用動態規劃來解,可是如今咱們是要學習動態規劃,所以請想一想如何用動態規劃來作?
答案:
一、過程爲一次一次的購買,每一次購買也許只買一本(這有三種方案),或者買兩本(這也有三種方案),或者三本一塊兒買(這有一種方案),最後直到買完全部須要的書。
二、最後一步我必然會在7種購買方案中選擇一種,所以我要在7種購買方案中選擇一個最佳狀況。
三、子問題是,我選擇了某個方案後,如何使得購買剩餘的書能用最少的錢?而且這個選擇不會使得剩餘的書爲負數。母問題和子問題都是給定三卷書的購買量,求最少須要用的錢,因此有「子問題重疊」,問題中三個購買量設置爲參數,分別爲i、j、k。
四、的確符合。
五、邊界是一次購買就能夠買完全部的書,處理方式請讀者本身考慮。
六、每次選擇最多有7種方案,而且不會同時實施其中多種,所以方案的選擇互不影響,因此有「子問題獨立」。
七、我能夠用minMoney[j][k]來保存購買第1卷i本,第2卷j本,第3卷k本時所需的最少金錢。
八、共有x * y * z 個問題,每一個問題面對7種選擇,時間爲:O( x * y * z * 7) = O( x * y * z )。
九、用函數MinMoney(i,j,k)來表示購買第1卷i本,第2卷j本,第3卷k本時所需的最少金錢,那麼有:
MinMoney(i,j,k)=min(s1,s2,s3,s4,s5,s6,s7),其中s1,s2,s3,s4,s5,s6,s7分別爲對應的7種方案使用的最少金錢:
s1 = 60 * 0.95 + MinMoney(i-1,j,k)
s2 = 60 * 0.95 + MinMoney(i,j-1,k)
s3 = 60 * 0.95 + MinMoney(i,j,k-1)
s4 = (60 + 60) * 0.9 + MinMoney(i-1,j-1,k)
s5 = (60 + 60) * 0.9 + MinMoney(i-1,j,k-1)
s6 = (60 + 60) * 0.9 + MinMoney(i-1,j,k-1)
s7 = (60 + 60 + 60) * 0.85 + MinMoney(i-1,j-1,k-1)
----第六節----代碼參考------
html
動態規劃雖然歸結爲是算法,但它給出的實際上是解決某一類問題的思路,並非有特定的代碼或者數學公式。java
剛開始接觸動態規劃都會比較難理解,我本身理解也不是很深入,本着通俗易懂的原則,作個記錄。算法
1.什麼樣的問題能夠用動態規劃數組
要用動態規劃的問題仍是有很明顯的特徵的,最顯著的就是子問題重疊,子問題重疊就是從最開始的問題引伸出來的ide
子問題都是和開始問題極其類似的,除了數據不一樣其餘問題形式都相同。拿採草藥問題爲例:函數
題目要求:學習
辰辰是個天資聰穎的孩子,他的夢想是成爲世界上最偉大的醫師。爲此,他想拜附近最有威望的醫師爲師。醫師爲了判斷他的資質,給他出了一個難題。醫師把他帶到一個處處都是草藥的山洞裏對他說:「孩子,這個山洞裏有一些不一樣的草藥,採每一株都須要一些時間,每一株也有它自身的價值。我會給你一段時間,在這段時間裏,你能夠採到一些草藥。若是你是一個聰明的孩子,你應該可讓採到的草藥的總價值最大。」
若是你是辰辰,你能完成這個任務嗎?優化
首先開始問題簡潔的說就是,在A時間內,要求採B個草藥,使其採到全部草藥的價值和最大。this
假設採了一株藥草,花費的時間爲D,先無論它的價值,那麼此時子問題來了:spa
在A-D時間內,要求採B-1個草藥,使其採到全部草藥的價值和最大,這就是子問題重疊。
還須要注意的一點就是,子問題相互獨立,所謂獨立就是各個子問題得出的結果不會影響到其餘問題的結果。好比
這裏,採和不採這二者之間沒有關聯,假設有兩我的都面臨這個採草藥的問題,他們都有一樣的時間,採一樣個數的
草藥。甲採了第一個草藥,乙沒有采,兩我的繼續採後面的草藥,此時乙不會由於甲採了草藥而減小了本身採藥的時
間,乙沒有采,因此採第二個的時候,乙仍是原來開始時擁有的時間。這就是子問題獨立。
2.怎麼思考問題
接着上面的例子,B個草藥每個面臨的問題都是菜或者不採,因此給這些草藥編號,假設B=5,那麼就是有
0,1,2,3,4,這五個草藥,這裏用0或者1開始均可以。假如採第0個,那麼此時時間減小了,還剩的採藥時間爲
原時間-採0花費的時間,採到的價值提升了=採0獲得的價值,假如不採第0個,時間不變,價值不變。無論
對第0個選擇哪一種操做,繼續採第二個,可選操做同上。
那不可能一直選下去,有兩個終止條件,第一是選到第4個之後,就沒有草藥再讓你採了,第二是所剩餘的時間已經
不夠採了,其實仔細想一想,只有第一個終止條件成立。由於對於當前正在採的第n個草藥,所剩餘的時間不夠,它還
是應該再去嘗試第n+1個,那剩餘時間和採第n+1所需時間比較,直到採到最後一個爲止。
從這以上咱們就能夠提取出改變的量有三個,採藥的時間,獲得的價值,採哪一個草藥。
說了這麼多,最重要的問題就是函數要怎麼寫,動態規劃與遞歸是分不開的,從子問題重疊那裏也能夠看出來,是要
不斷的遞歸函數,最終獲得結果的。函數的參數就是從分析問題中提取出來的改變的量,但並不所有都是。
這裏採草藥獲得的價值,是函數的返回值,不做爲參數,時間和哪一個草藥做爲參數。有人可能會奇怪爲何把價值當
作返回值,由於題目要的就是得出最大價值。
狀態轉移方程若是初學者去細摳這個狀態的定義,是如何轉移的,我的認爲很差理解,建議拿幾個動態規劃的題目,
先了解思路,明白動態規劃究竟是怎麼樣實現的,再去深思它的狀態和狀態轉移。
還有一點須要注意:狀態轉移方程必定會遞歸調用上面的函數!
採n的式子:f(還有的時間-採n須要的時間,採第n+1個)+採n獲得的價值
不採n的式子:f(還有的時間,採第n+1個)
回到問題,要求的是最大可採到的價值,這是個比較性的問題,須要選擇一個最大值,在什麼中選擇呢?就是在採n
和不採n中得到的價值進行選擇。在開始採的時候,每採一個都會有這樣的問題,採第0個之後再繼續採後面的草藥獲
得的價值較多,仍是不採第0個把時間留着後面的草藥。固然你想是想不出來的,只能兩條路都走,最後比較選一條
價值高的。因此咱們最終的狀態轉移方程就有了:
max.(f(還有的時間-採n須要的時間,採第n+1個)+採n獲得的價值,f(還有的時間,採第n+1個) )
上面只是分析了一種狀況,那就是採第n個草藥時,剩餘的時間充足,才能夠有選擇的採或者不採,那若是剩餘的時
間已經不夠採第n個草藥了,就只能選擇不採n的式子,也就是這裏須要作一個判斷。
寫出狀態轉移方程之後,問題就基本解決了,可是當數據不少也很大的時候,須要使用備忘錄方法提升效率。
3.如何使用備忘錄
備忘錄說白了就是記錄已經算出來的數據,把這些數據保存下來,這樣就不須要每次都進行大量的計算。
能夠說對於複雜的數據來講,沒有備忘錄的動態規劃不具備任何優點。
上面沒有講到「最優子結構」的問題,最優子結構就是經過實現子問題的最優獲得開始問題的最優解。
聯繫到上面,咱們比較兩個式子從而獲得最大值,可是這個最大值並無出來,而是把問題繼續日後拋,在對
第0個草藥選擇採與不採之後,把問題拋給第1個草藥。就這樣一直日後面拋,直到第4個。那麼在這個過程當中,
經過計算產生的數據,若是保存下來,在其餘選擇分支須要用到這個數據時,就能夠直接獲取。
保存格式因題而異,通常數據就是二維INT數組,數組的大小能夠設置成容許範圍的無限大,好比int[10000][10000]
但這樣的設置,在數據很少的狀況下,會很浪費空間。因此就想辦法把範圍大小和已經的條件關聯起來。
回想在採藥的問題中,它最初已經了草藥的個數,每一個草藥所須要的時間和價值,已經總的採藥時間。
怎麼從這些數據中選擇,就是設置範圍的關鍵。這須要考慮到作備忘錄時的條件,也就是往數組裏面放些什麼,
在這道題目中,須要把採哪一個草藥和時間放進去,注意這裏的時間並非須要的時間,而是在採選擇是否採這個草藥
前剩餘的時間。而後選擇一個較大值,時間明顯比哪一個草藥要大,因此int[time*2][time*2]
這裏*2是由於每一個藥草對應兩個不一樣的狀況。
1 package 洛谷; 2 3 import java.io.BufferedReader; 4 import java.io.IOException; 5 import java.io.InputStreamReader; 6 import java.util.ArrayList; 7 import java.util.Iterator; 8 9 public class P1048採藥 10 { 11 ////////////////////////////////////////////////////////////////////////////// 12 // // 13 // 採摘n f(剩餘時間-採摘時間,採n+1個)+n獲得的價值 // 14 // 不採摘n f(剩餘時間,採n+1個) // 15 // max( f(剩餘時間-採摘時間,採n+1個)+n獲得的價值 , f(剩餘時間,採n+1個)); // 16 // // 17 ///////////////////////////////////////////////////////////////////////////// 18 // 最優子結構」、「子問題重疊」、「邊界」和「子問題獨立」 [動態規劃] 19 static int totaltime; 20 static int tatalnumber; 21 static ArrayList<Tbao> tbaos=new ArrayList<>();//全部草藥 22 static int t[][];//記憶數組 23 public static void main(String[] args) throws IOException 24 { 25 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); 26 String string[] = bufferedReader.readLine().split(" "); 27 totaltime = Integer.parseInt(string[0]); 28 tatalnumber = Integer.parseInt(string[1]); 29 for (int i = 0; i < tatalnumber; i++) 30 { 31 string = bufferedReader.readLine().split(" "); 32 tbaos.add(new Tbao(Integer.parseInt(string[0]), Integer.parseInt(string[1]))); 33 } 34 t=new int [totaltime*2][totaltime*2]; 35 System.out.println(find(0, totaltime)); 36 } 37 38 private static int find(int i,int time)//第幾個 剩餘時間 39 { 40 int result=0; 41 if (i==tatalnumber)//最後一個了 出口 42 { 43 return 0; 44 } 45 if(t[i][time]!=0) 46 { 47 return t[i][time]; 48 } 49 Tbao tbao=tbaos.get(i); 50 if(time>=tbao.time) 51 { 52 result= Math.max(find(i+1,time-tbao.time)+tbao.cost, find(i+1,time)); 53 } 54 else 55 { 56 result= find(i+1,time) ; 57 } 58 t[i][time]=result; 59 return result; 60 } 61 62 63 } 64 class Tbao 65 { 66 int time; 67 int cost; 68 public Tbao(int time, int cost) 69 { 70 super(); 71 this.time = time; 72 this.cost = cost; 73 } 74 @Override 75 public String toString() 76 { 77 return "Tbao [time=" + time + ", cost=" + cost + "]"; 78 } 79 80 }