\( \newcommand{\ord}[1]{\mathcal{O}\left(#1\right)} \newcommand{\abs}[1]{\lvert #1 \rvert} \newcommand{\floor}[1]{\lfloor #1 \rfloor} \newcommand{\ceil}[1]{\lceil #1 \rceil} \newcommand{\opord}{\operatorname{\mathcal{O}}} \newcommand{\argmax}{\operatorname{arg\,max}} \newcommand{\str}[1]{\texttt{"#1"}} \)

動態規劃

Dynamic Programming

什麼是動態規劃(DP)

動態規劃與分而治之的基本想法是相近的,都是把問題不斷分割成比較小的子問題,直到我們可以直接解決,之後再由子問題的結果推出大問題的答案。但在遞迴的過程中,經常會發生同樣一個子問題被處理了好幾次的狀況

動態規劃的特性

通常會使用動態規劃來解決的問題分成兩種:最佳化問題與計數問題,而能套用動態規劃方法的問題通常要具備以下幾個條件

1.最佳子結構

在把問題分割成比較小的子問題時,先假設我們能得到比較小的問題的答案,我們要能夠藉由某種過程從這些答案推出母問題的答案。有時問題的子問題會互相影響,或著母問題最佳化時子問題不一定達到最佳,需要特別注意每個推論的正確性

2.重複子問題

在有重複的子問題,即會重複利用相同的子問題的結果時,使用動態規劃才有節省時間的意義,否則只是徒然浪費空間儲存不需要再次使用的結果而已

3.無後效性

解決動態規劃問題的過程可以分為幾個階段,使得每個階段所做的決策只與之前階段的結果有關,而與尚未出現的狀態無關。也就是說將每個狀態與其決策時相關的狀態以有向邊相連後,所產生的圖是一個有向無環圖(DAG)

名詞定義

狀態:描述一個我們將要去解決的子問題之性質
階段:一些性質相近,可以同時處理的狀態集合
決策:每個階段中做出的一些選擇性的行動,也就是我們的程式所需要做出的選擇
狀態轉移方程:如何藉著其他狀態的答案算出某一狀態的答案,通常以遞迴式表示
對於一個動態規劃算法,通常其時間複雜度為狀態數*狀態轉移花費,而其空間複雜度至少為在某時刻所有已解決且之後可能被使用到的狀態數

動態規劃問題的解決方法

一般來說比較簡單的DP用以下幾個步驟就能解決了,難一點的DP目前你們應該是還不會碰到的

設計狀態

即使用幾個特性去描述一個子問題,使可能出現的狀態種數不會太多(別忘了動態規劃的時間複雜度直接取決於狀態數),且狀態轉移過程中所有可能被使用到的子問題都能被這幾個特性去具體精確的描述。這也是解決動態規劃問題過程中最重要的一個步驟

推導狀態轉移方程

即如何利用其他狀態的答案推出這個狀態的方法。值得注意的是狀態轉移的過程必須要能建立一個決策順序,也就是不能有A 狀態的答案依賴於B 狀態,而B 狀態亦直接或間接的依賴於A 狀態的狀況發生,注意邊界條件

分析算法的正確性及時空花費

若時空花費無法滿足題目需求,則必須再想方法去優化你的算法的過程

例題講解

以最常遞增子序列為例

最常遞增子序列問題

給定一個數列$S$,請刪掉最少量的數字使得剩下的序列是嚴格遞增的(稱為LIS)。例如數列$S=\{4,2,3,7,5,6,8\}$的LIS是$\{2,3,5,6,8\}$,長度為5。
有兩個子題,子題一$|S| \leq 1000$,子題二$|S| \leq 10^6$
子題二比較難,二分搜+貪心+DP都會用到,我們先來處理子題一吧

1.設計狀態

通常我們會把狀態名稱定為$dp$。
這題的狀態很簡單,令$dp(n)$為以$S[n]$為結尾最長的遞增子序列即可
最後取$max(dp(0) \sim dp(|S|-1))$作為答案

2.推導狀態轉移方程

根據觀察,可以知道狀態間有以下的關係 $$dp(n)=max(\{dp(i):i < n \; and \; S[n]>S[i]\})+1$$ 注意$max(\{\})=0$

別忘了邊界條件$dp(0)=1$

3.分析算法的正確性及時空花費

每個狀態都儲存的話,要花費$\ord{n}$的空間
狀態轉移$\ord{n}$,有$n$個狀態,故會花費$\ord{n^2}$的時間

實作方式

分為top-down與bottom-up兩種,其中bottom-up即依照一定的決策順序枚舉每個狀態,對其依序套用狀態轉移方程求出結果。而top-down 的方法即對遞迴過程中每個狀態的答案做紀錄,並在再次呼叫時直接回傳答案,通常於其狀態的決策順序難以枚舉或狀態並不會完整擴展時較為適合。

狀態壓縮

有的時候你的狀態會是一個集合,這種題目為了讓你可以在時間內AC,會讓集合的大小約為$10 \sim 20$左右
我們可以用一個int或long long之類的東西來表示集合,然後可以用位元運算來進行操作

題目

本周有五題,每題code都不會太長,但是需要大量思考,各位可以在無聊或是上課不想聽或是跟男/女朋友約會時好好想一想喔!
第一題 注意如何從狀態回推答案
第二題
第三題
第四題 注意這題的狀態有包含集合
第五題 注意枚舉狀態的順序