學習筆記——線性動態規劃
線性動態規劃是OIer學習DP的基礎,只有學好了線性DP,才能更好的理解之後學習的揹包、區間DP、樹形DP等一系列難題,那麼廢話不多說,開始我們的學習之旅吧!
1.DP的概念
1.${\color{Red}\colorbox{White}{子問題重疊性}}$:DP演算法把原問題視作若干個重疊子問題的逐層遞進,每個子問題的求解過程都構成一個“階段”。在完成前一個階段的計算後,DP才會執行下一階段的計算。
2.${\color{Red}\colorbox{White}{無後效性}}$:為了保證DP中每一階段的計算能夠按順序、不重複的進行,DP要求已經求解的子問題不受後續階段的影響。
我們身為OIer自然要更加重視知識的關聯性。運用類比的思想,我們可以發現DP與圖論這兩個看似毫不相干的兩個知識竟然有著千絲萬縷的聯絡。
${\color{Blue}\colorbox{White}{無後效性的另一種理解}}$:${\color{Black}\colorbox{Yellow}{DP對狀態空間的遍歷}}$構成了一張${\color{Black}\colorbox{Yellow}{有向無環圖(簡稱DAG)}}$,遍歷順序就是該有向無環圖的一個${\color{Black}\colorbox{Yellow}{拓撲序}}$,該DAG中節點對應問題中的狀態,圖中的邊對應狀態之間的轉移,轉移的選取對應DP中的決策。
3.${\color{Red}\colorbox{White}{最優子結構性質}}$:
(1).${\color{Blue}\colorbox{White}{狹義理解}}$:下一階段的最優解能夠由前面各階段子問題的最優解匯出
(2).${\color{Blue}\colorbox{White}{廣義理解}}$:DP在階段計算完成時,只會在每個狀態上保留與最終解集相關的部分代表資訊,這些代表資訊應該具有可重複的求解過程,並能夠匯出後續階段的代表資訊。
4.${\color{Red}\colorbox{White}{總結}}$
綜上所述,狀態、階段和決策是構成DP演算法的三要素,而子問題重疊性、無後效性和最優子結構性質是問題能用DP求解的三個基本條件。
2.線性DP的定義
如果一個DP演算法的狀態包含多個維度,但在每一個維度上都具有線性變化的階段,則該DP演算法稱為線性DP。
3.線性DP的核心思想
首先,我們可以先用自己的方式進行題意簡述
舉個栗子(LIS問題--最長上升子序列)
題意簡述 | 給定一個長度為$N$的數列$A$,求數值單調遞增的子序列的最長長度 |
---|---|
狀態表示 | 令$F[i]$表示以$A[i]$結尾的最長上升子序列的長度 |
階段劃分 | 子序列的結尾位置(數列$A$中的位置,從前到後) |
轉移方程 | $F[i]=\max(F[i],F[j]+1)(0 \leqslant j < i,A[j]<A[i]$) |
邊界條件 | $F[0]=0$ |
目標 | $\max(F[i])(1 \leqslant i \leqslant N$) |
4.線性DP的一些優化技巧
1.在設計DP的狀態轉移方程時,可以以如何計算出一個狀態
(直接)的形式給出,也可以考慮一個已知狀態應該更新那些後續階段的未知狀態(間接)。它們各有千秋,視具體題目選擇合適的方法更有利於解題。
2.滾動陣列真的好用:我們對於一些可能MLE的DP狀態進行分析,如果我們發現這一狀態的DP只與前幾狀態的DP有關,就可以只定義這個數+1個的該維度空間(舉個栗子:若i的狀態只與i-1的狀態有關,則對於i的維度可以只開到2的空間,迴圈使用0和1)。但是滾動陣列會覆蓋掉之前求的狀態,如果只用求最終狀態則建議使用滾動陣列進行優化,若要求每一個狀態的最優解,千萬別用!(不過這類題一般不會爆MLE,要不然就無解了)(滾動陣列具體用法參照T7我的程式碼)
3.在實現狀態轉移方程時,要注意觀察決策集合的範圍隨狀態的變化情況,若決策集合有規律的變化,就可以考慮維護一個新的變數記錄決策集合的當前資訊,避免重複掃描,把轉移的複雜度降低一個量級。(要栗子的話就是T2我用優化打了個N方DP過了NlogN的題(與資料水也有很大的關係))
別問為什麼沒有程式碼,DP這毒瘤玩意根本沒有模板
好了,最後我再扔幾道題就閃吧!
1.線性DP的入門水題(一篇比較了DP與貪心、搜尋、記搜複雜度的好題解)(碼了個10min競速程式碼)
通過這道水題,我知道了兩點:
(1).我的手速真的不行,碼程式碼碼不快(慢慢練吧)
(2).沒有立刻想到最優解(明顯從下傳到上比從上傳到下更快,因為前者只用輸出$ans[1][1]$,而後者要多一個$\Theta(n)$的迴圈求最大值),要多鍛鍊自己的思維水平。
2.棧+二分、樹狀陣列版二維偏序吊打DP的偽DP題(通俗易懂的棧+二分題解)(還不會的樹狀陣列版二維偏序)(DP思路+DP常數優化+我的程式碼)
5.四種牌分別處理的線性DP(有點揹包的味道)(講的不錯的題解)(我的程式碼)
6.第一個自己獨立做出的DP綠題(O(N)做法的題解)(二分做法的NlogN的題解)(我的程式碼)
隨手取模真的很重要,不然100pts->60pts。以後遇到這類題也不用太慌,先明確自己設定的DP陣列每個維度都代表著什麼(最好在程式碼中的相應陣列下寫一點註釋),然後考慮未知的狀態可以由那些已經求出的狀態推出(可以在草稿紙上寫出DP方程式),明確思路後再碼程式碼。
8.構造新數列求最長不下降子序列+推新結論的噁心題(有動圖的良心題解)(碼了半天的程式碼(一個晚上+半個上午))
比較難的DP題真的就是在比耐心和細心。
問題一:本題運用正難則反的思想,對於暫時毫無思路的問題,可以考慮構造新數列進行求解(可以將題面簡化為一堆關係式,然後對其進行化簡,找出關係式中的共同點,在將其用一個新陣列儲存,可以極大的簡化程式碼、降低複雜度和輔助思考)。
那個推新結論是人能想到的嗎?
問題二:對於一些更難的問題,可以多畫圖輔助思考,排除一些矛盾的情況,找到一些類似的情形,然後轉化為DP的形式進行狀態轉移方程的推導。
10.雙DP陣列+字首和優化的水題(講的不錯的題解(只看思路就可以打程式碼))(我的程式碼)
最後再挖兩個坑,以後再填吧!(畢竟現在不是刷黑題的時候)