FHVirus
一個弱弱的病毒,感染症狀為壓常毒瘤綜合併發症。
9/22 校隊培訓
FHVirus
這堂課的內容主要是解題的想法、一般性的概念(尤其是DP),
相關的題目變化多端,請多多做題練習。
國小生都寫的出來(不是嘴砲)
有 N 個線段 [li,ri],
求最多能選幾個線段
使得他們互不相交
有 N≤105 個人站成一排,每個人左右手各拿一張牌 ai,bi,
每個人的分數為「前面所有人的 a 乘積除以自己的 b」。
已知第一個人固定且不計分,
求一種排序,使得所有人分數最大值最小。
你有 109 單位的時間以及 N≤105 份工作。
每一份工作有一個截止時間 Di≤109和一個報酬 Pi。
每一單位時間你可以做一份工作,求最大獲利。
某國硬幣有 N 種面額 a1,a2,⋯,aN
現在給你要用這些幣值湊出 X 元,
求最少需要幾個硬幣。
我懶的放題敘
我懶的放題敘
當子問題重複,可以紀錄子問題答案,避免重複計算
時間複雜度也會變好!
(不會重複的話就 DQ 就好)
DP 的問題形式,也就是 DP 狀態
要先定好狀態(想清楚要問什麼)才能找答案
例如費氏數列,狀態 dpi 代表第 i 項
這步驟通常是最難的(知道要問什麼問題)
有點玄學有點通靈
多做題也許有用
DP 的子問題,也就是 DP 轉移來源
代表「現在這個問題的答案可能是從哪個子問題來的」
在最佳化問題中,來源不一定單一
例如費氏數列,狀態 dpi 的來源為 dpi−1,dpi−2
要特別小心不能再分下去的狀態,也就是邊界狀態
例如費氏數列的邊界狀態就是 dp0=dp1=1
DP 的子問題合併,也稱為 進行轉移
代表「從子問題得到現在狀態的答案」
在最佳化問題中,來源不一定單一
例如費氏數列,轉移就是 dpi=dpi−1+dpi−2
轉移通常可以寫成式子表示
DP 優化也多從式子出發
強烈建議想題的時候把式子寫出來
DP 的問題答案紀錄,也會被稱為 DP 表格
通常開個陣列紀錄之類的
例如費氏數列要求到第 N 項就開長度為 N 的陣列
也有人會用 DFS + unordered_map
但效率奇差,不建議使用
除非你是要驗題或是唬爛
你爽就好,但某些題目不同作法難度會不同
Bottom-Up
(Push)
Bottom-Up
(Pull)
Top-Down
(DFS style)
常數稍大
當 DP 長的像這樣:dpi,[⋯]=f(dpi−1,[⋯])
也就是所有的 i 都只會從 i−1 轉移
那在算完 i 之後 i−1 就可以丟掉了(省空間!)
例如費氏數列,如果只要求第 N 項可以這樣寫:
long long a = 1, b = 1, c, N = 49;
// a = f(i-2), b = f(i-1), c = f(i)
for(int i = 2; i <= N; ++i){
c = a + b;
a = b;
b = c;
}
如果要滾的是一整個陣列,也可以用指標或 vector 來做
swap(vector<T>, vector<T>) 是 O(1) 的喔!
例題:N×M 方格,有多少種方式從左上到右下
// sc for source
vector<int> dp(M + 1, 0), sc(M + 1, 0); // 記得大小要開對!
sc[0] = 1; // base case
for(int i = 1; i <= N; ++i){
for(int j = 1; j <= M; ++j){
// do things
}
swap(dp, sc);
}
cout << sc[M] << '\n'; // 最後會多 swap 一次!
滾動好處多多!
當 Greedy 不對就代表
「這個問題不能只由這個子問題轉移」
換句話說,Greedy 就是可以證明只有一個轉移來源的 DP
reference: baluteshih @2021TOI 1! 簡報
強烈建議各位去翻翻看
By FHVirus