hiho一下 第五周 Hihocoder #1037 : 数字三角形
#1037 : 數字三角形
時間限制:10000ms 單點時限:1000ms 內存限制:256MB問題描述
小Hi和小Ho在經歷了螃蟹先生的任務之后被獎勵了一次出國旅游的機會,于是他們來到了大洋彼岸的美國。美國人民的生活非常有意思,經常會有形形色色、奇奇怪怪的活動舉辦,這不,小Hi和小Ho剛剛下飛機,就趕上了當地的迷宮節活動。迷宮節里展覽出來的迷宮都特別的有意思,但是小Ho卻相中了一個其實并不怎么像迷宮的迷宮——因為這個迷宮的獎勵非常豐富~
于是小Ho找到了小Hi,讓小Hi幫助他獲取盡可能多的獎品,小Hi把手一伸道:“迷宮的介紹拿來!”
小Ho選擇的迷宮是一個被稱為“數字三角形”的n(n不超過200)層迷宮,這個迷宮的第i層有i個房間,分別編號為1..i。除去最后一層的房間,每一個房間都會有一些通往下一層的房間的樓梯,用符號來表示的話,就是從第i層的編號為j的房間出發會有兩條路,一條通向第i+1層的編號為j的房間,另一條會通向第i+1層的編號為j+1的房間,而最后一層的所有房間都只有一條離開迷宮的道路。這樣的道路都是單向的,也就是說當沿著這些道路前往下一層的房間或者離開迷宮之后,小Ho沒有辦法再次回到這個房間。迷宮里同時只會有一個參與者,而在每個參與者進入這個迷宮的時候,每個房間里都會生成一定數量的獎券,這些獎券可以在通過迷宮之后兌換各種獎品。小Ho的起點在第1層的編號為1的房間,現在小Ho悄悄向其他參與者弄清楚了每個房間里的獎券數量,希望小Hi幫他計算出他最多能獲得多少獎券。
小Hi拿到迷宮的介紹,仔細想了想,問道:“你自己有什么思路么?”
小Ho想了想,道:“反正每次只有兩種選擇,我就選通向有更多獎券的房間的那條路唄~”
小Hi笑了笑:“你這叫盲目貪心,如果我是迷宮的創造者,一定在第2層的第1個房間放10張獎券,第2個房間一張不放,然后在第3層的第3個房間放999張獎券,這樣你就因為占一點小便宜就失掉了大頭,你說是不是?”
小Ho一驚:“似乎是的!切記不可因小失大,那……既然我們都是學計算機的學生,不如我們用計算機枚舉一下所有可能的路徑,然后找到最優的那一條?”
小Hi點了點頭:“自然是要用計算機來解決這個問題,不過你得先計算一下總共有多少條可能的路徑,估算一下運行時間,這樣才能確定能不能在迷宮節結束之前計算完是不是~”
“我來算算,從出發點開始,每次我都有兩種選擇,而我總共要做n-1次選擇,也就是2的n-1次方,而n最大可能是200,也就是說有2^199條路徑……哪怕每條路徑我只用一次運算就可以算出它的獎券總數,我畢業之前都肯定算不出來了吧QAQ”小Ho得出了一個非常憂傷的結論。
“是的,但是這種搜索的方法其實是可以有方法進行優化的哦~”小Hi循循誘導著。
“什么樣的方法?”
“別急,聽我慢慢說~”
 
 
“我們可以來分分情況,我們要搜索所有的路徑,然后依次計算每條路徑的收益,這是我們現在的問題模型是不是?”小Hi問道。
小Ho點頭:“仔細分析這個迷宮的話,我們有兩種搜索方法,一種是深度優先搜索,就像這幅圖畫的一樣,我們先試著順著每個房間的第一條路一直下去,一直走到最后一層,然后返回至倒數第二層,選擇第二條路走到最后一個房間,然后返回到倒數第三層,選擇第二條路走到倒數第二層,然后選擇第一條路走到最后一層……期間維護已經走過的房間的獎券之和sum,然后每次到達最后一層的房間的時候將sum與全局最優解ans進行比較,如果sum>ans的話就用sum替換ans。這樣在所有路徑都計算過一遍之后,Ans中存儲的就是我們要的答案了~~而第二種……”
“先別說第二種,我們來看看這種方法有什么辦法優化么~”小Hi打斷了小Ho的喋喋不休。
“哦,但是該怎么優化呢?”小Ho問道。
“老規矩,我們來看看這個過程中有什么冗余計算~”小Hi還是那一套說辭,也不擔心小Ho聽了這么多遍聽厭:“看這張圖,當你枚舉到綠色的‘-》右-》左’這樣一條路徑的時候,是不是發現它和紅色的‘-》左-》右’這條路徑一樣都到達了第三層的第二個房間?”
“是的!”
“在你枚舉到綠色路徑的時候,是不是紅色路徑已經被枚舉過了?”小Hi接著問道。
“沒錯!“
“那么你看,綠色路徑的獎券和是8,紅色路徑的獎券和是10,而你們都處于了相同的結點——第3層的第2個房間上,這時候無論你綠色路徑接下來延續出什么樣的路徑,我將從第3層第2個房間開始的這一段連在紅色路徑之后的話,獎券和都會要比綠色路徑要多?”小Hi繼續問。
“嗯……對的~”
“而這些路徑中的最優值都肯定不超過Ans,不然Ans就會在當時變得和它們一樣,這不就說明了綠色路徑無論接下來如何走,都不可能對Ans造成任何變化,那我們是不是完全可以不進行接下來的計算了呢?”小Hi做出了最后的結論。
“是的……為了進行這樣的優化,我們需要記錄一個值best(i, j)——當前搜索過的路徑中到達第i層第j個房間時最多能獲取多少獎券,然后在每次進入一個房間的時候,都檢查當前的sum與best(i, j)的大小關系,如果sum小于等于best(i, j)的話,就沒有必要繼續搜索了呢。比如在之前的例子中,當通過綠色路徑到達第3層第2個房間的時候,best(i, j)=10,而sum = 8,所以是沒有必要繼續往下搜索的。”聰明的小Ho很快就將這個算法總結了出來。
“而這種做法,由于是通過之前的記憶來避免不必要的搜索,所以被大家稱為‘記憶化搜索’”小Ho笑道:“這樣,只要不是那種非常壞的卡你的情況——比如第i層第j個房間的獎券數是j這種,都能夠在O(n^2)的復雜度通過了呢。”
“而如果我先右再左呢?”小Ho不死心。
“那我就第i層第j個房間的獎券數是i-j”小Ho邪惡的笑著:“其實你不用糾結這個,你只要隨機一下就能夠保證幾乎所有情況都是O(n^2)的時間復雜度,或者說平均復雜度是O(n^2),但是你的最壞情況復雜度肯定是降不下去的,所以還是老老實實想想別的方法吧~比如之前你想說的第二種方法?”
“與深度優先搜索相對應的自然就是寬度優先遍歷啦~如果我們用<i, j, k>表示到達第i行的第j個房間,當前獲得的獎券數為k這樣一個狀態,rewards(i, j)表示第i層第j個房間中的獎券數,那么遍歷方式就是利用一個隊列,先將?, 1, rewards(1, 1)>這個狀態放入隊列中。然后每次取出隊首<i, j, k>,如果i==n,就用k更新Ans,不然就將它代表的房間所能到達的兩個房間的狀態<i + 1, j, k + rewards(i + 1, j)>和<i + 1, j + 1, k + rewards(i + 1, j + 1)>放入隊尾,直到整個隊列清空,這時候Ans就是我們要的答案。”小Ho緩緩道來。
“那你覺得這個地方又有什么可以優化的呢?”
“本來我還沒想到,但是你說了之前深度優先搜索的問題之后我就有點思路了,你看狀態(2, 1, 8)和狀態(2, 2, 6)在處理時一個會拓展出(3, 2, 10)這個狀態,而另一個會拓展出(3, 2, 8)這個狀態,但是就像我們之前的分析那樣(3, 2, 8)這個狀態是沒有什么用的,它之后無論怎么走,換成(3, 2, 10)按它一樣的走法肯定獲得的獎券數要更多。”小Ho觸類旁通,一下就找到了問題所在:“所以我可以像深度優先搜索那樣用一個best(i, j)記錄當前搜索過的路徑中到達第i層第j個房間時最多能獲取多少獎券,然后在一個狀態(i, j, k)想要入隊的時候,判斷k與best(i, j)的大小,如果k小于等于best(i, j)就沒有必要進隊了~”
“看來你還陷在了死胡同里,你這樣在最壞情況復雜度根本沒有變化不是?”小Hi無奈道:“你仔細想想,按照寬度優先搜索的順序,是不是一個狀態(i, j, k)在拓展出(i', j', k')的時候,(i', j', k')要么從來沒有入隊列,要么就仍然在隊列里?”
“唔...這個問題看來,基本上就是這樣一層一層的順序出隊列的,那還是那個問題,如果我在判斷狀態(i, j, k)是否應該入隊列的時候,發現了k大于best(i, j),這個時候如果我再去判斷一下隊列中是否有一個(i, j, k')的狀態。如果沒有,就將(i, j, k)入隊列,不然因為k'大于k,那么就像我們之前分析的那樣(i, j, k')這個狀態時無用的,不如就用k去替換掉狀態(i, j, k')中的k',這樣也會減少運算不是?”
“沒錯~而且你仔細分析的話,每個房間都只會入隊列和出隊列一次,這樣就保證了平均情況時間復雜度和最壞情況時間復雜度都是O(n^2)哦!~”
“原來是這樣,那么現在問題差不多解決了呢!”小Ho高興道:“趕緊算出來,我好去拿獎品。”
“急什么!你從這之中可學到了什么知識么?”小Hi攔住了急沖沖的小Ho,問道。
“啊?”
 
“且不說深度優先這樣一個最壞情況下仍然有問題的方法,我們來說這個寬度優先搜索,我們能夠使用best(i, j)來進行優化,無非是因為這個問題存在這樣兩個性質。”小Hi道。
“第一便是無論我是通過怎么樣的方式到達第i層第j個房間的,我之前做出的選擇不會對我之后的選擇做出限制與優待。就像如果我規定至少要向右走3次,那么狀態就不僅僅是(i, j, k)這樣,還要加上一個已經向右走的次數t,那么你覺得還能就直接像我們之前的方法進行計算么?如果到達第i層第j個房間的路上向右走過3次了,那么之后的走法就沒有任何限制,不然就仍會有一個至少要向右走一定次數的限制。這樣對于兩個狀態(i, j, k, l)和(i, j, k', l'),我們就不能夠直接判斷出那個狀態是更加好的。
“沒錯~其實就是best(i, j)有兩個關鍵字進行參考,而這樣的話就不是任意兩個狀態都可以進行比較的了。”小Ho總結道。
“這樣的性質被我們稱為無后效性,顧名思義,當前的抉擇不會對后面抉擇產生影響。”
“那第二個性質呢?”
“第二個性質被我們稱為重復子問題,在我們之前的例子中,我們將從起點出發到走出迷宮的最優路分解成了兩個子問題,其一是從第2層的第1個房間走出迷宮的最優路,其二是從第2層的第2個房間走出迷宮的最優路,只要能算出這兩個部分的最優值,我就可以知道從起點出發到走出迷宮的最優路。”小Hi道:”而按照這樣的方法,這兩個子問題都有一個相同的子問題——從第3層的第2個房間出發走出迷宮的最優路。”
“這就是重復的子問題了,而我們的做法就是,重復的子問題只計算一次!也就是說我們要先計算出從起點到達第3層的第2個房間的最優線路,然后再考慮接下來怎么走~”小Ho感覺自己漸漸的把握到了關鍵之處。
“對的,所以綜合這樣兩個性質,我們可以考慮用best(i, j)表示從起點出發,走到第i層第j個房間出發最多可以獲得的獎券數。”小Hi提出了一個新的定義:“那么我們要求的答案,便是所有best(n, j)中最大的那一個對不對?
“是的,那該如何求這個呢……”小Ho思索著:“第i層第j個房間可以由第i-1層第j個房間和第i-1層第j-1個房間到達,所以best(i, j) = max{best(i - 1, j - 1), best(i - 1, j)} + rewards(i, j)這樣么?”
“是的,這個公式便是我們俗稱的狀態轉移方程,你要做的,就是按照這樣的順序,依次計算出每一個房間的best(i, j),然后在最后統計所有best(n, j)的最大值,就可以得到答案了。”
“這個過程和寬度優先搜索的確很像,但是由于順序是確定了的,我可以用一個兩重循環,外層是i從1到n枚舉層數,內層是j從1到i枚舉房間編號,就可以直接進行計算了!”小Ho道。
“你這個公式的邊界情況還有待考慮嘛,比如j=1的時候就不需要取max了,直接使用best(i - 1, j)計算即可~”小Hi指出了小Ho存在的一個問題。
“哦~那么j=i的時候也有相似的處理是吧。”
“是的呢!所以快去寫程序吧~”小Hi笑了笑:“抓緊時間,迷宮節就要結束了哦!”
輸入
每個測試點(輸入文件)有且僅有一組測試數據。
每組測試數據的第一行為一個正整數n,表示這個迷宮的層數。
接下來的n行描述這個迷宮中每個房間的獎券數,其中第i行的第j個數代表著迷宮第i層的編號為j的房間中的獎券數量。
測試數據保證,有100%的數據滿足n不超過100
對于100%的數據,迷宮的層數n不超過100
對于100%的數據,每個房間中的獎券數不超過1000
對于50%的數據,迷宮的層數不超過15(小Ho表示2^15才3萬多呢,也就是說……)
對于10%的數據,迷宮的層數不超過1(小Hi很好奇你的邊界情況處理的如何?~)
對于10%的數據,迷宮的構造滿足:對于90%以上的結點,左邊道路通向的房間中的獎券數比右邊道路通向的房間中的獎券數要多。
對于10%的數據,迷宮的構造滿足:對于90%以上的結點,左邊道路通向的房間中的獎券數比右邊道路通向的房間中的獎券數要少。
輸出
對于每組測試數據,輸出一個整數Ans,表示小Ho可以獲得的最多獎券數。
樣例輸入總結
以上是生活随笔為你收集整理的hiho一下 第五周 Hihocoder #1037 : 数字三角形的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: hiho一下 第四周 Hihocoder
- 下一篇: hiho一下 第六周 Hihocoder
