演算法與排列組合
建國中學 賴昭勳
提問/回答的網站: slido
#68610
暖身大水題
從一個有6顆不同的球的袋子中任意取3顆球,有幾種取法?
今天的主題:動態規劃
(Dynamic Programming)
DP是什麼?
當一個問題可以被分成很多個問題合併起來的時候(遞迴關係),則可以將小問題的結果存起來,去往後推大問題的答案。
經典範例
求費氏數列的第n項
時間複雜度 O(n)
遞迴表示方法?
f(1) = 1, f(2) = 1
f(n) = f(n - 1) + f(n - 2)
直接寫遞迴
int fib(int n) {
if (n == 1 || n == 2) return 1;
else return fib(n - 1) + fib(n - 2);
}
感覺有點沒效率?
DP: 存取小問題的答案
int f[1000];
int fib(int n) {
f[1] = f[2] = 1;
for (int i = 3;i <= n;i++) {
f[i] = f[i - 1] + f[i - 2];
}
return f[n];
}
當問到的數字已經被算過了,就直接拿來用。
時間複雜度:線性 O(n)
這聽起來有點廢話...?
(也是很經典的)例題
有個 n=8 階的樓梯,每次可以往上走1, 2, 3階,共有幾種走完8階的方法?
可在slido 回答!
概念和費氏數列一樣!
dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3]
int dp[n + 1];
dp[0] = 0, dp[1] = 1, dp[2] = 2;
for (int i = 3;i <= n;i++) {
dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3];
}
DP 三要素
-
定義
-
狀態轉移方式
-
初始狀態(邊界條件)
小結論
DP 是一個解題手法。對於一個有遞迴關係的題目,先將小問題的答案存下來,再去解決大問題。
與排列組合的關聯
有一些題目,直接排組下去太難了!
例如:https://tioj.ck.tp.edu.tw/problems/1354
題:有一隻青蛙在A, B, C, D四個石頭之間跳。青蛙從A開始,每次跳到不同的石頭,問跳n次之後停在A的方法有幾種?
ex. N = 5
DP 可以維護好幾種東西
- 定義:令 dp[i][0] 代表跳了 i 次之後,不在 A 的方法數,dp[i][1] 代表跳了 i 次回到A的方法。
- 轉移方式:dp[i][1] = dp[i - 1][0], dp[i][0] = 3 * (dp[i - 1][1] + 2 * dp[i - 1][0])
- 初始條件:dp[0][1] = 1, dp[0][0] = 0
再一個例題
有 n=10 個排成一排的座位。
定義一種坐法是「尷尬」為:原本坐的人兩兩不相鄰,但是再加一個人就一定會出現相鄰的狀況。
有幾種「尷尬」的坐的方法?
思考的方向
考慮「增加一個位置時」可以出現哪些情況,因為這很可能是dp(遞迴)會用到的。
參考做法
-
定義:dp[i][0] 代表長度為 i 且最後一項是空的時候有幾種方法。dp[i][1] 代表長度為 i 且最後一項有坐人的方法數。
-
轉移:考慮以下情況。
因此可以得到:
dp[i][0] = dp[i - 1][1]
dp[i][1] = dp[i - 2][1] + dp[i - 3][1]
參考做法
3. 打表,寫好初始條件
i = | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|
dp[i][0] | 0 | 1 | 1 | 1 | 2 | 2 | 3 | 4 | 5 |
dp[i][1] | 1 | 1 | 1 | 2 | 2 | 3 | 4 | 5 | 7 |
答案:dp[n][0] + dp[n][1]!
其實,dp 不一定要是一維的。
原來這也是dp
從A到B,只往右或往上,有幾種走法?
狀態轉移->把兩個路徑的方法數加起來
將n個相同的箱子放入m顆不同的球,有幾種方法? (n, m <= 200)
ex. n = 3, m = 5
把dp 的定義想的活一點
把dp 的定義想的活一點
若 j < i (球數小於箱子數),dp[i][j] = 0
否則,有兩種方法達到dp[i][j]:
從dp[i - 1][j - 1] 多一個箱子跟球,球在那個新的箱子裡。
從dp[i][j - 1] 中加一個球進去,有 i 個位置可以加。
因此:dp[i][j] = dp[i - 1][j - 1] + i * dp[i][j - 1]
i\j | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
1 | 1 | 1 | 1 | 1 | 1 |
2 | 0 | 1 | 3 | 7 | 15 |
3 | 0 | 0 | 1 | 6 | 25 |
最後一題!
有一個2xn 的表格,現在要將1~2n放進去,必須符合:左邊格子<右邊格子且
上面格子<下面格子。 有幾種方法?
ex: n = 4
註:這題討論的是O(n^2)的解
1 | 2 | 5 |
---|---|---|
3 | 4 | 6 |
1 | 3 | 4 |
---|---|---|
2 | 5 | 6 |
2 | 3 | 4 |
---|---|---|
1 | 5 | 6 |
解決dp問題的重點
轉換問題!
假設我們已經知道一個橫排要選哪些數
(ex. 1 3 4 5), 那麼那些數只有一種排法!
此問題等價於:將1~2n的數字標示 n 個 A 和n 個B,代表那個數字要寫在上面還是下面。則有幾種作法讓任一前綴中 A的個數>=B的個數?
AAABBB, ABAABB (O)
AABBBB, ABBABB (X)
看似一維的題目也可以用二維dp
dp [i][j] 代表,長度為 i,
A的個數 - B的個數 = j 的方法數
這樣的話,答案就是 dp[n][0]
轉移:dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j + 1]
j\i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
8 | 1 | |||||||
7 | 1 | |||||||
6 | 1 | 7 | ||||||
5 | 1 | 6 | ||||||
4 | 1 | 5 | 20 | |||||
3 | 1 | 4 | 14 | |||||
2 | 1 | 3 | 9 | 28 | ||||
1 | 1 | 2 | 5 | 14 | ||||
0 | 1 | 2 | 5 | 14 |
什麼時候該用?
可以有「遞迴」關係的問題!
一般的排列組合還是用正常方法寫吧!
所以DP到底在幹嘛?
聽起來只是一種遞迴的另一個稱呼...
DP 的應用
LIS, LCS, Edit Distance
無法Greedy的最佳解問題
Bitset DP
單調隊列、四邊形優化、Aliens DP...
從很簡單到超級難的題目,
從APCS(第四題) 到 IOI,
DP無所不在。
更多DP跟排組的東西
-
卡特蘭數
-
錯排
-
如何計算C(n, k)...
-
H(n, k)
謝謝各位的聆聽
DP 與 排組 (社團)
By justinlai2003
DP 與 排組 (社團)
- 883