Dynamic Programming (1)
by music960633
課程內容 課程內容
• 1. 什麼是 DP ?
• 2. Top down / Bottom up
• 3. 「狀態」、「狀態轉移」
• 4. 取餘數
什麼是 DP 什麼是 DP
• Dynamic programming ,動態規劃
• 將一個問題分成許多子問題,遞迴求解,最後再由子問題的答案 得到原本問題的答案 ( 類似 D&C)
• 相同的子問題會出現不只一次 ( 與 D&C 不同 )
• 將子問題的答案記錄起來
• 避免對相同的問題再遞迴一次
• 用空間換取時間
Example 1 Example 1
• 用 1*2 的骨牌填滿 2*n 的格子,共有幾種排法?
Example 1 Example 1
• 用 1*2 的骨牌填滿 2*n 的格子,共有幾種排法?
• Sol:
• 以 f(n) 表示填滿 2*n 格子的方法數
• 觀察最後一格放置骨牌的情形:
f(n-1) f(n-2)
Example 1 Example 1
• 用 1*2 的骨牌填滿 2*n 的格子,共有幾種排法?
• Sol:
• 以 f(n) 表示填滿 2*n 格子的方法數
• 觀察最後一格放置骨牌的情形:
• f(n)=f(n-1)+f(n-2)
• 初始條件: f(1)=1, f(2)=2
• 費氏數列
Example 1 Example 1
• 實作
• 最簡單遞迴版本
• 缺點:太慢
• 時間複雜度: O(f(n))
• 解決方法:使用陣列記錄答案
4 3
2 1
2
5
3
2 1
Top down Top down
• 將答案記錄起來,如果遇到相同的問題,則直接查表
• 時間複雜度: O(n)
4 3
2 1
2
5
3 3
Bottom up Bottom up
• 用迴圈先把答案全部算出來
• 時間複雜度: O(n)
Top down vs Bottom up Top down vs Bottom up
• Top down:
• 只要知道遞迴式就可以了,剩下交給遞迴跑
• code 一般為遞迴函式
• 注意遞迴過深
• Bottom up
• 子問題一定要比母問題先跑到 ( 注意迴圈跑法 )
• code 一般為 for 迴圈
Example 2 Example 2
• 將 n 個排成一列的格子塗上紅、綠、藍三種顏色,且藍綠不可相
鄰,問有幾種塗法?
Example 2 Example 2
• 將 n 個排成一列的格子塗上紅、綠、藍三種顏色,且藍綠不可相 鄰,問有幾種塗法?
• Sol:
• 設 f(n) 為塗 n 格的方法數
• f(n)=...
• 糟糕,遞迴式不知道怎麼寫
Example 2 Example 2
• 將 n 個排成一列的格子塗上紅、綠、藍三種顏色,且藍綠不可相 鄰,問有幾種塗法?
• Sol:
• 設 f(n,0) 為塗 n 格,且最後一格為紅色的方法數
• 設 f(n,1) 為塗 n 格,且最後一格為綠色的方法數
• 設 f(n,2) 為塗 n 格,且最後一格為藍色的方法數
• 遞迴式
• f(n,0)=f(n-1,0)+f(n-1,1)+f(n-1,2)
• f(n,1)=f(n-1,0)+f(n-1,1)
• f(n,2)=f(n-1,0)+f(n-1,2)
• 初始條件: f(1,0)=f(1,1)=f(1,2)=1
• 最後答案: f(n,0)+f(n,1)+f(n,2)
Example 2 Example 2
• Top down
Example 2 Example 2
• Bottom up
「狀態」、「狀態轉移」
「狀態」、「狀態轉移」
• 狀態
• Example 1: f(n) 表示填滿 2*n 格子的方法數
• Example 2:
• f(n,0) 為塗 n 格,且最後一格為紅色的方法數
• f(n,1) 為塗 n 格,且最後一格為綠色的方法數
• f(n,2) 為塗 n 格,且最後一格為藍色的方法數
• 在以上兩個例子中,我們都定義了函式參數所代表的意義,或是陣列索引值所 代表的意義
• f(n,m) 中的 n,m
• dp[n][m] 中的 n,m
• 狀態:用一些數字來「唯一」表示一個子問題
「狀態」、「狀態轉移」
「狀態」、「狀態轉移」
• 狀態轉移
• Example 1: f(n)=f(n-1)+f(n-2)
• Example 2:
• f(n,0)=f(n-1,0)+f(n-1,1)+f(n-1,2)
• f(n,1)=f(n-1,0)+f(n-1,1)
• f(n,2)=f(n-1,0)+f(n-1,2)
• 在以上兩個例子中,我們可以用一些方程式表示如何由其他的子問題結 果得到一個問題的答案
• 狀態轉移:如何由其他的狀態得到某個狀態的值
「狀態」、「狀態轉移」
「狀態」、「狀態轉移」
• 定義狀態時該注意的事
• 1. 狀態是否太多? ( 太多陣列開不下 )
• 2. 是否能導出狀態轉移式? ( 如 Example 2)
• 定出狀態且找出狀態轉移式之後
• 1. 時間複雜度是否合理?
• 2. 若不合理,能不能對狀態轉移做優化?
• 3. 若還是不行,試試看用其他方法定義狀態
• 如何找到正確的狀態定義方式?
• 靠經驗
• 靠靈感
Example 3 Example 3
• 用 1*2 和 1*3 的 L 型骨牌填滿 2*n 的格子,共有幾種排法?
• 定義狀態
• f(n) 表示填滿 2*n 格子的方法數
• 狀態是否太大? No
• 狀態轉移
• 同樣考慮最後一格的放法,放 1*2 的 case 已在 Example 1 討論過,因此 只討論放 L 型骨牌的 case
Example 3 Example 3
• 因為兩種情況是上下對稱的,因此只考慮一種方向
• 最後放 1*2 : f(n-1)+f(n-2)
• 最後放 L 型:
f(n-3) f(n-4) f(0)=1
Example 3 Example 3
• 因為兩種情況是上下對稱的,因此只考慮一種方向
• 最後放 1*2 : f(n-1)+f(n-2)
• 最後放 L 型: 2[f(n-3)+f(n-4)+...+f(1)+f(0)]
• 狀態轉移
• f(n)=f(n-1)+f(n-2)+2[f(n-3)+f(n-4)+...+f(1)+f(0)]
• O(n) 時間轉移
• 是否合理? O(n2) 好像不太好
• 能不能優化呢?
Example 3 Example 3
• 優化 f(n)=f(n-1)+f(n-2)+2[f(n-3)+f(n-4)+...+f(1)+f(0)]
• 使用變數記錄前綴和
• 若使用 bottom up ,可以用一個變數 (tmp) 記錄 f(0)+f(1)+...+f(n-3)
,如此可讓轉移變成 f(n)=f(n-1)+f(n-2)+2tmp , O(1) 轉移
• 化簡轉移式
• f(n-1)=f(n-2)+f(n-3)+2[f(n-4)+f(n-5)+...+f(1)+f(0)]
• f(n-1)+f(n-3)=f(n-2)+2[f(n-3)+f(n-4)+f(n-5)+...+f(1)+f(0)]
• 得到 2[f(n-3)+f(n-4)+...+f(1)+f(0)]=f(n-1)-f(n-2)+f(n-3)
• 轉移式變成 f(n)=2f(n-1)+f(n-3) , O(1) 轉移
Example 3 Example 3
• 另外一種狀態定義方式
• f(n,0): 鋪滿 2*n 的方法數
• f(n,1): 鋪滿 2*n 且下面多一格的方法數
2*f(2,1)
Example 3 Example 3
• 另外一種狀態定義方式
• f(n,0): 鋪滿 2*n 的方法數
• f(n,1): 鋪滿 2*n 且下面多一格的方法數
• 狀態轉移
• f(n,0)=f(n-1,0)+f(n-2,0)+2f(n-2,1)
• f(n,1)=f(n-1,0)+f(n-1,1)
• 初始狀態
• f(0,0)=1, f(0,1)=0
• f(1,0)=1, f(1,1)=1
• f(2,0)=2, f(2,1)=2
取餘數 取餘數
• 在計算方法數的題目中,我們常看到「請輸出答案除以 M 的餘 數」
• 以 Example 3 來說, f(28)=1914332891 ,如果 n 到很大 (100
萬 ) ,則一定要用到大數,但大部分不想要那麼麻煩,因此會要 求輸出餘數即可
• 加減乘法可以直接取餘數,除法則不行
• 小心在 mod 之前就 overflow ,因此建議開 long long( 尤其乘
法 )
取餘數 取餘數
• 若 Example 3 要求輸出答案除 1000007 的餘數
Example 4 Example 4
• 給一個正整數陣列,現在要從裡面取出一些數,但兩數不能相鄰
,求取出數字和的最大值
• Ex: arr[] = {1,4,2,3,5} ,則取出 4 和 5 有最大總合 9
Example 4 Example 4
• 定義狀態
• 假設索引值從 1 開始
• f(n) 為從 arr[1] 到 arr[n] 中取出數字,且有取到 arr[n] 的總合最大值
• 狀態轉移
• 因為數字不為負的,因此選擇的兩個數字之間一定間格 1 或 2
• 如果取了 arr[n] ,則上一個取到的數字為 arr[n-2] 或 arr[n-3]
• f(n)=max(f(n-2),f(n-3))+arr[n]
• 最後答案
• max(f(N), f(N-1))
Example 4 Example 4
• 另外一個解法
• 定義狀態
• f(n,0) 為從 arr[1] 到 arr[n] 取數字,且沒有取到 arr[n] 的總合最大值
• f(n,1) 為從 arr[1] 到 arr[n] 取數字,且有取到 arr[n] 的總合最大值
• 狀態轉移
• 如果沒有取到 arr[n] ,則 arr[n-1] 不論有沒有取都合法
• 如果有取到 arr[n] ,則一定不能取 arr[n-1]
• f(n,0)=max(f(n-1,0), f(n-1,1))
• f(n,1)=f(n-1,0)+arr[n]
• 最後答案
• max(f(N,0), f(N,1))