<style>
.reveal {
font-size: 30px;
}
</style>
<style>
.yellow {
color: yellow;
}
</style>
# DP 複習與練習 I
#### **Gino**
---
### 第一次講課是不是要自我介紹啊
![](https://i.imgur.com/auhGU9w.png)
![](https://i.imgur.com/z5AGGrf.png)
---
# [題單在此](https://hackmd.io/@penguin71630/DPproblem)
---
### 課前提醒
這堂課的定位是複習
主要會再講解一次 DP 的性質還有複習一些重要的經典題
難度約中偏易,然後我可能會飆車
如果上到一半發現有不懂的地方可以在 Discord 的閒聊專區或問題討論區發問
或是回去翻 [DP I](https://drive.google.com/file/d/1BT5_HotYej62klPdfrgjrUkqViJTl-3k/view?usp=sharing) 跟 [DP II 的簡報](https://drive.google.com/file/d/1fVCA6AJ65Z7ZQUQzEfs7JbkAjJRKAnVP/view?usp=sharing)
---
### 再讓我囉嗦一下
然後因為臨時調課
這堂課的簡報是今天爆肝生出來的
內容可能不是很多還請見諒(**#每日關心Foxyy的簡報進度**)
預計一個小時就會講完,剩下兩個小時讓大家刷題 + 提問 + 在 dc 哈拉
很放鬆的一堂課 OwO
---
### 再讓我囉嗦一下下下下下
DP 練習還有一堂課
下次上課會從題單裡面挑一些有趣的題目(我勉強會寫的)來講解
然後如果三生有幸我能夠學會那些 DP 優化的話也會講解
###### 四邊形優化好難qwq
---
# 目錄
- 什麼是 DP
- DP 經典題
- 暖身:二維路徑 DP
- LIS
- 二分搜優化
- 資結優化
- 求 LIS 數量
- 求最小字典序 LIS
- 背包問題
- 0/1 背包
- 滾動陣列
- 無限背包
- 有限背包
- 練習時間
---
# DP 是啥
----
### 舉個栗子
有 $N$ 階的樓梯,從第 $1$ 階開始,每次可以走 $1$ 階或走 $2$ 階,請問剛好走到第 $N$ 階有多少種方法?
----
### 舉個栗子
有 $N$ 階的樓梯,從第 $0$ 階開始,每次可以走 $1$ 階或走 $2$ 階,請問剛好走到第 $N$ 階有多少種方法?
- 定義 $dp[i]$ 為剛好走到第 $i$ 階的方法數
- $dp[0] = 1, dp[1] = 1$
- $dp[i] = dp[i-1] + dp[i-2] \quad \forall i \geq 2$
- 答案就是 $dp[N]$
----
- $dp[i]$:**狀態**
- $dp[0] = 1, dp[1] = 1$:**初始狀態**
- $dp[i] = dp[i-1] + dp[i-2]$:**狀態轉移式**
----
## DP 的性質
----
1. **重複子問題**:同一個狀態可能會被用到很多次
![](https://i.imgur.com/OIEJ63k.png)
----
$f(2)$ 被呼叫兩次,算過一次就紀錄起來,之後可以直接查,不需要重算
![](https://i.imgur.com/H02Wi0U.png)
DP 問題的本質是分治與遞迴,而 DP 的精髓在於算過的答案不要重算
- 紀錄子問題 (Memoization)
----
2. **最佳子結構**:大問題的答案可以從小問題的答案推出
「$f(4)$ 可以從 $f(3)$ 跟 $f(2)$ 推得」
----
3. **無後效性**:每個狀態的轉移來源不能相互依賴
「要算 $f(a)$ 需要先算出 $f(b)$,但要算 $f(b)$ 需要先算出 $f(a)$ ...?」
----
3. **無後效性**:每個狀態的轉移來源不能相互依賴
把每個狀態想像成節點,轉移關係想像成邊
則合理的狀態定義必定會使所有狀態**形成一個 DAG**
而**可行的轉移順序**便是該圖的其中一種**拓樸排序**
![](https://i.imgur.com/FW0Tdwu.png)
----
## DP 的步驟
1. **觀察問題,定義好狀態**
- 可以先不管複雜度,但要考慮好所有情況
- 題目列出的條件都可能可以拿來當狀態的參數
2. **推出轉移式**
- 好好考慮該如何切成小問題
- 切出來的小問題必須包含所有可能情況,同時又必須互相獨立
- 發現很難轉移 $\Rightarrow$ 修改狀態定義
3. **找到一個合理的轉移順序**
- 費式數列:從 $f(0)$ 推到 $f(N)$
- 區間 DP:從小區間推到大區間
- 位元 DP:從小集合推到大集合(或是從 $0$ 到 $2^N$)
4. **優化狀態/轉移過程**
----
## 實作方式
- Top down
```cpp=
int f[100];
int F(int x) {
if (x <= 1) return f[x] = 1;
return f[x] = F(x-1) + F(x-2);
}
```
- Bottom up
```cpp=
int f[100];
f[0] = f[1] = 1;
for (int i = 2; i < 100; i++) {
f[i] = f[i-1] + f[i-2];
}
```
----
- Top down
- 使用時機
- 有用到的狀態很稀疏時
- 轉移確定無後效性,但合理的轉移順序很難找時
- [旅行推銷員問題](https://cses.fi/problemset/task/1690)
- Bottom up
- 優點:狀態稠密時,常數較小
- 優點:好套優化
- 常見 DP 優化都是把要考慮的轉移來源開個資結處理(前綴和陣列/線段樹/BIT/李超線段樹),或是利用狀態的單調性二分搜、均攤優化(單調隊列/四邊形優化)
----
Bottom up 又可以分兩種
- pull:把答案從子問題「拉上來」
```cpp
f[i] = f[i-1] + f[i-2];
```
- push:把子問題答案主動「推」到大問題
```cpp
f[i+2] += f[i];
f[i+1] += f[i];
```
視實作方便度決定,大部分都是用 pull,少數題目用 push 比較好寫[例如這題](https://codeforces.com/group/3Xn3T5DO0a/contest/362327/problem/D)(KMP+DP)
---
# 經典題目 I — 二維路徑DP
暖身一下
----
[2018北市賽 幸運表格](https://tioj.ck.tp.edu.tw/problems/2182)
$n \times m$ 的地圖,每格上面有權重
求從某個格子開始往右/往下移動直到超出邊界,經過的格子權重和最大是多少?
----
如果題目是從左上角走到右下角的答案
- $dp[i][j] =$ 走到 $(i, j)$ 的最大權重和
- $dp[i][j] = max($從上面走來$,$ 從左邊走來$)$
- $dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + w[i][j]$
- 答案 $dp[n][m]$
----
考慮原題的移動方式,修改一下轉移
- $dp[i][j] = max($從上面走來$,$ 從左邊走來$,$ 從這格開始$)$
- $dp[i][j] = max(dp[i-1][j], dp[i][j-1], 0) + w[i][j]$
- 答案 $max(dp[n][1\sim m], dp[1\sim n][m])$
---
# 經典題目 II — LIS
----
經典到不能再經典
- 長度 $N$ 的序列 $a$,輸出 $a$ 當中最長非嚴格遞增子序列的
1. 長度
2. 個數
3. 最小字典序的 LIS
- [subtask 1](https://neoj.sprout.tw/problem/253/): $N \leq 5000$
- [subtask 2](https://neoj.sprout.tw/problem/254/): $N \leq 10^5$
.
###### P.S. 行尾不要輸出多餘空白
----
先求長度
- $dp[i] \equiv$ 結尾位置為 $i$ 的 LIS 長度。
- $dp[i] = max_{j < i \land a[j] \leq a[i]} \{ dp[j] \} + 1$
- 答案是:$max_{1 \leq i \leq N} \{ dp[i] \}$
轉移直接用迴圈的話複雜度 $O(N^2)$
----
### $O(N \log N)$ 作法一
設 $t[len]$ 為所有 $dp[i] = len$ 中最小的 $A[i]$
| $A[i]$ | 4 | 2 | 6 | 5 | 1 | 7 | 3 | 9 |
| ------- | --- | --- | --- | --- | --- | --- | --- | --- |
| $dp[i]$ | 1 | 1 | 2 | 2 | 1 | 3 | 2 | 4 |
| $len$ | 1 | 2 | 3 | 4 |
| -------- | --- | --- | --- | --- |
| $t[len]$ | 1 | 3 | 7 | 9 |
----
- 有了 $t$ 要怎麼算 $dp[i]$?
----
- 有了 $t$ 要怎麼算 $dp[i]$?
- 從所有 $\leq A[i]$ 的 $t[len]$ 中,找出最大的 $len$。
- $dp[i] = maxlen + 1$
----
- 有了 $t$ 要怎麼算 $dp[i]$?
- 從所有 $\leq A[i]$ 的 $t[len]$ 中,找出最大的 $len$。
- $dp[i] = maxlen + 1$
- 如果所有 $t[len]$ 都 $> A[i]$,那就代表前面不能接任何數字
- $\Rightarrow$ $dp[i] = 1$
----
Claim:$t[len]$ 會單調遞增
Proof:反證法
- 設存在 $j < i$ 使得 $t[j] > t[i]$
- 觀察下圖,會發現 $t[j]$ 不滿足定義 $\Rightarrow$ 矛盾
![](https://i.imgur.com/FGxFNiX.png)
----
Claim:$t[len]$ 會單調遞增
找 $maxlen$?
----
Claim:$t[len]$ 會單調遞增
找 $maxlen$?
## 二分搜!
----
### $O(N \log N)$ 作法二
觀察轉移式:$dp[i] = max_{j < i \land a[j] \leq a[i]} \{ dp[j] \} + 1$
也就是說,可能的轉移點 $j$ 滿足:
1. $j < i$
2. $A[j] \leq A[i]$
所有的 $j$ 當中最大的 $dp[j]$ 就是轉移點。
----
可以用一個區間查詢的資料結構維護**區間 $dp$ 值最大值**。
查詢 $dp[i]$ 的最佳轉移點可以用 $query(1, i-1)$ 解決。
----
可以用一個區間查詢的資料結構維護**區間 $dp$ 值最大值**。
查詢 $dp[i]$ 的最佳轉移點可以用 $query(1, i-1)$ 解決。
但還有一個條件:$A[j] \leq A[i]$?
----
可以用一個區間查詢的資料結構維護**區間 $dp$ 值最大值**。
查詢 $dp[i]$ 的最佳轉移點可以用 $query(1, i-1)$ 解決。
但還有一個條件:$A[j] \leq A[i]$?
## DP 順序:從 $A[i]$ 越小的開始做!
----
可以用一個區間查詢的資料結構維護**區間 $dp$ 值最大值**。
查詢 $dp[i]$ 的最佳轉移點可以用 $query(1, i-1)$ 解決。
但還有一個條件:$A[j] \leq A[i]$?
## DP 順序:從 $A[i]$ 越小的開始做!
這樣可以保證 $query(1, i-1)$ 得到的答案必定滿足 $A[j] \leq A[i]$
----
資結可以使用線段樹
但其實這題條件很特別,可以用 BIT
- 詢問區間只有前綴
- 單點修改時只會把 $0$ 改成 $dp[i]$(數字只會改大不會改小)
----
求 LIS 個數?
----
求 LIS 個數?
$cnt[i]$ 表示**以 $i$ 結尾**的最長遞增子序列**數量**
| $A[i]$ | 4 | 2 | 6 | 5 | 1 | 7 | 3 | 9 |
| ------- | --- | --- | --- | --- | --- | --- | --- | --- |
| $dp[i]$ | 1 | 1 | 2 | 2 | 1 | 3 | 2 | 4 |
| $cnt[i]$| 1 | 1 | 2 | 2 | 1 | 4 | 2 | 4 |
----
假設已經算出 $dp[i]$,考慮求 $cnt[i]$
則 $cnt[i] = \sum cnt[j]$,$j$ 滿足:
1. $j < i$
2. $A[j] \leq A[i]$
3. $dp[j] = dp[i] - 1$
![](https://i.imgur.com/kr2MheH.png)
----
- 使用作法二,維護最大值的同時也維護**滿足該最大值的數量**
- 詢問的時候除了回傳最大值也一併回傳**滿足該最大值的數量**
- 如何維護交給大家練習
----
求最小字典序 LIS?
----
- 求最小字典序的作法
- Greedy 地由前面往後掃
- 能先拿就拿
- DP 回溯解的方向
- 由後往前回推
兩個方向不同,怎麼辦?
----
- 求最小字典序的作法
- Greedy 地由前面往後掃
- 能先拿就拿
- DP 回溯解的方向
- 由後往前回推
兩個方向不同,怎麼辦?
## 把原本的序列倒過來做 DP!
----
- 求最小字典序的作法
- Greedy 地由<span class="yellow">後面往前掃</span>
- 能先拿就拿
- DP 回溯解的方向
- 由後往前回推
## 把原本的序列倒過來做 DP!
原本求 LIS 改成求 LDS(最長遞減子序列)
----
### 練習
- [裸題](https://neoj.sprout.tw/problem/254/)
- [NEOJ 421](https://neoj.sprout.tw/problem/421/)
- [TOI 初選 pC](https://tioj.ck.tp.edu.tw/problems/2195)
---
# 經典題目 III — 背包問題
----
背包問題的變形:
- 0/1 背包(最原始的)
- 無限背包
- 有限背包
----
[0/1 背包 I — 狀態的維度【考慮第 $i$ 個,總重量】](https://atcoder.jp/contests/dp/tasks/dp_d)
$N$ 個物品有各自的重量 $w_i$ 和價值 $v_i$
背包重量上限 $W$,求最大價值?
- $N \leq 100$
- $W \leq 10^5$
- $v_i \leq 10^9$
----
- $dp[i][w]$ 考慮到第 $i$ 個物品,且此時背包已經裝了總重 $w$ 的物品的最大總價值
----
- $dp[i][w]$ 考慮到第 $i$ 個物品,且此時背包已經裝了總重 $w$ 的物品的最大總價值
- $dp[i][w] = max($拿$i,$ 不拿$i)$
- $dp[i][w] = max(dp[i-1][w-w_i] + v_i, dp[i-1][w])$
![](https://i.imgur.com/96ogSjT.png)
----
- $dp[i][w]$ 考慮到第 $i$ 個物品,且此時背包已經裝了總重 $w$ 的物品的最大總價值
- $dp[i][w] = max($拿$i,$ 不拿$i)$
- $dp[i][w] = max(dp[i-1][w-w_i] + v_i, dp[i-1][w])$
- 答案會是 $max_{0 \leq w \leq W} \{dp[N][w]\}$
----
- $dp[i][w]$ 考慮到第 $i$ 個物品,且此時背包已經裝了總重 $w$ 的物品的最大總價值
- $dp[i][w] = max($拿$i,$ 不拿$i)$
- $dp[i][w] = max(dp[i-1][w-w_i] + v_i, dp[i-1][w])$
- 答案會是 $max_{0 \leq w \leq W} \{dp[N][w]\}$
- 初始狀態和實作細節交給大家練習
- $dp$ 表格大小開 $N \times W$
- 寬只到 $W$ 是因為超過 $W$ 根本不是合法狀態
時間跟空間複雜度 $O(NW)$
----
空間 $O(NW)$ 好像太緊了
----
觀察到每個狀態 $dp[i][w]$ 的轉移來源都只在上一列
除此之外其他狀態根本用不到
----
### 滾動陣列 I
- 開兩個陣列 $dp0, dp1$ 交替使用
- 假設現在在 $dp1$,那麼就從 $dp0$ 轉移
- 假設現在在 $dp0$,那麼就從 $dp1$ 轉移
![](https://i.imgur.com/bK9cuUC.png)
空間複雜度 $O(W)$
----
好還要更好!
兩個陣列 $\Rightarrow$ 一個陣列
----
### 滾動陣列 II
- 只開一個陣列 $dp$
- 新的狀態要覆蓋舊的狀態
- 確保每個新的狀態的轉移來源都不能被覆蓋到
----
### 滾動陣列 II
- 只開一個陣列 $dp$
- 新的狀態要覆蓋舊的狀態
- 確保每個新的狀態的轉移來源都不能被覆蓋到
- DP 順序:從 $dp[W]$ 倒著做到 $dp[0]$
![](https://i.imgur.com/KNVYgvR.png)
----
[0/1 背包 II — 狀態的維度【考慮第 $i$ 個,總價值】](https://atcoder.jp/contests/dp/tasks/dp_e)
$N$ 個物品有各自的重量 $w_i$ 和價值 $v_i$
背包重量上限 $W$,求最大價值?
- $N \leq 100$
- $W \leq 10^9$
- $v_i \leq 10^3$
----
- $dp[i][v]$ 考慮到第 $i$ 個物品,且此時背包已經裝了總價值 $v$ 的物品的最小總重
----
- $dp[i][v]$ 考慮到第 $i$ 個物品,且此時背包已經裝了總價值 $v$ 的物品最小總重
- $dp[i][v] = min($拿$i,$ 不拿$i)$
- $dp[i][v] = min(dp[i-1][v-v_i] + w_i, dp[i-1][v])$
----
- $dp[i][v]$ 考慮到第 $i$ 個物品,且此時背包已經裝了總價值 $v$ 的物品最小總重
- $dp[i][v] = min($拿$i,$ 不拿$i)$
- $dp[i][v] = min(dp[i-1][v-v_i] + w_i, dp[i-1][v])$
- 假設所有東西總價值和 $sv = \sum_{i=1}^{N} v_i$。
- 取答案的方法:
- 從 $dp[N][sv]$ 開始倒著掃到 $dp[N][0]$
- 只要掃到某個 $v$ 滿足 $dp[N][v] \leq W$,$v$ 就是答案
----
- $dp[i][v]$ 考慮到第 $i$ 個物品,且此時背包已經裝了總價值 $v$ 的物品最小總重
- $dp[i][v] = min($拿$i,$ 不拿$i)$
- $dp[i][v] = min(dp[i-1][v-v_i] + w_i, dp[i-1][v])$
- 假設所有東西總價值和 $sv = \sum_{i=1}^{N} v_i$。
- 取答案的方法:
- 從 $dp[N][sv]$ 開始倒著掃到 $dp[N][0]$
- 只要掃到某個 $v$ 滿足 $dp[N][v] \leq W$,$v$ 就是答案
- $dp$ 表格大小開 $N \times sv$
時間複雜度 $O(N \sum_{i=1}^{N} v_i)$
----
無限背包
$N$ 個物品有各自的重量 $w_i$ 和價值 $v_i$,且數量都有無限多個
背包重量上限 $W$,求最大價值?
- $N \leq 100$
- $W \leq 10^5$
- $v_i \leq 10^9$
----
- $dp[i][w]$ 考慮到第 $i$ 個物品,且此時背包已經裝了總重 $w$ 的物品的最大總價值
- $dp[i][w] = max(dp[i][w-w_i] + v_i, dp[i-1][w])$
要多少有多少,所以拿了一個 $i$ 之後不必回到 $i-1$ 的狀態
![](https://i.imgur.com/YyHaDTq.png)
----
[有限背包](https://codeforces.com/group/3Xn3T5DO0a/contest/350019/problem/D)
![](https://i.imgur.com/jvWymWO.png)
----
- $dp[i][w]$ 考慮到第 $i$ 個物品,且此時背包已經裝了總重 $w$ 的物品的最大總價值
- $dp[i][w] = max($拿$0$個$i,$拿$1$個$i,$拿$2$個$i, ...,$拿$c_i$個$i)$
- $dp[i][w] = max(\\dp[i-1][w],\\dp[i-1][w-w_i] + v_i,\\dp[i-1][w-2w_i] + 2v_i,\\...\\dp[i-1][w-c_i \cdot w_i] + c_i \cdot w_i)$
![](https://i.imgur.com/Oiy6ibp.png)
複雜度 $O(NW\cdot max(c_i))$
----
- 把第 $i$ 個物品看成 $c_i$ 個獨立的物品
- 直接一起做 0/1 背包
----
- 把第 $i$ 個物品看成 $c_i$ 個獨立的物品
- $N$ 個物品都這樣做,直接一起做 0/1 背包
複雜度 $O(NW\cdot max(c_i))$ 還是沒變
----
### 優化
- 把第 $i$ 個物品拆成 $1$ 個 $i$,$2$ 個 $i$,$4$ 個 $i$,...
- 一直用 $2$ 的冪次拆直到不夠拆,餘下的 $i$ 綁在一起。
- $N$ 個物品都這樣拆,把它們一起做 0/1 背包
----
### 優化
- 把第 $i$ 個物品拆成 $1$ 個 $i$,$2$ 個 $i$,$4$ 個 $i$,...
- 一直用 $2$ 的冪次拆直到不夠拆,餘下的 $i$ 綁在一起。
- $N$ 個物品都這樣拆,把它們一起做 0/1 背包
複雜度 $O(NW\cdot \log(max(c_i)))$
----
### 優化
- 把第 $i$ 個物品拆成 $1$ 個 $i$,$2$ 個 $i$,$4$ 個 $i$,...
- 一直用 $2$ 的冪次拆直到不夠拆,餘下的 $i$ 綁在一起。
- $N$ 個物品都這樣拆,把它們一起做 0/1 背包
複雜度 $O(NW\cdot \log(max(c_i)))$
**拆的方法只要保證能湊出 $[0, c_i]$ 之間任意數量的物品**
**接下來 DP 就會自動幫你算出最好的答案**
----
- 有限背包因為轉移點有時限($\because$ 物品數量有限)
- 所以可以套單調隊列優化
- 這裡不細講,忘記的可以回去看 [DP II 的簡報](https://drive.google.com/file/d/1fVCA6AJ65Z7ZQUQzEfs7JbkAjJRKAnVP/view?usp=sharing)
----
### 練習
- [2021 附中校隊培訓模競 pE](https://tioj.ck.tp.edu.tw/problems/2222)
- [NEOJ 159](https://neoj.sprout.tw/problem/159/)
---
提問時間與練習時間 :D
有人想唱歌或分享酷酷的科技可以開麥 OvO
或是看 **@Foxyy** 直播製作下禮拜的簡報
{"metaMigratedAt":"2023-06-16T20:28:54.029Z","metaMigratedFrom":"YAML","title":"DP 複習與練習 I","breaks":true,"slideOptions":"{\"transition\":\"fade\",\"spotlight\":{\"enabled\":false}}","contributors":"[{\"id\":\"ac1507e0-f05c-4708-bdd2-c56d13fb0dbb\",\"add\":12367,\"del\":1385}]"}