FHVirus
一個弱弱的病毒,感染症狀為壓常毒瘤綜合併發症。
by FHVirus
黑魔法訓練營
黑魔法訓練營|DP & Greedy
如果已經會上課內容的話,可以寫看看下一頁的題單。
有問題隨時都可以發問。
雖然預期是沒有人會想要跟講師講話
講義藏有彩蛋,
無聊可以找找看 ><
黑魔法訓練營|DP & Greedy
有空可以寫寫看各個講義裡的例題和習題,也可以挑戰看看最下面的比賽喔。
黑魔法訓練營|DP & Greedy
黑魔法訓練營|DP & Greedy
給定一個長度為 \(N\) 的非負整數序列 \(A\) ,
求 \(A\) 的最大子序列和。
很水對吧。
黑魔法訓練營|DP & Greedy
時空複雜度要求:\(O(N)\) / \(O(N)\)
黑魔法訓練營|DP & Greedy
給定一個長度為 \(N\) 的整數序列 \(A\) ,
求 \(A\) 的最大子序列和。
還是很水對吧。
時空複雜度要求:\(O(N)\) / \(O(N)\)
給定一個長度為 \(N\) 的整數序列 \(A\) ,
求 \(A\) 的最大不連續子序列和。
好像不那麼水了?
黑魔法訓練營|DP & Greedy
不連續的意思是:對於所有 \(1 \le i < N\) 的 \(i\) ,
\(i\) 和 \(i+1\) 只能選一個。
時空複雜度要求:\(O(N)\) / \(O(N)\)
黑魔法訓練營|DP & Greedy
不,不是那個。
再更笨一點。
枚舉 \(2 ^ N\) 種每一個序列元素要不要拿的情況,
時間複雜度 \(O(N \cdot 2 ^ N)\) 。
假設我們知道哪個要取,哪個不要取,
那麼我們就可以用 \(O(N)\) 時間計算總和。
黑魔法訓練營|DP & Greedy
把 DFS 過程畫出來可能會長這樣:
0
00
\(\vdots\)
\(\vdots\)
不拿
\(\vdots\)
\(\vdots\)
1
\(\vdots\)
\(\vdots\)
\(\vdots\)
\(\vdots\)
拿
01
10
11
000
001
010
011
100
101
110
111
黑魔法訓練營|DP & Greedy
最暴力無腦的作法複雜度當然很差。
如果要改善複雜度的話,
算過的東西不要重複算。
哪些是被重複算到的東西呢?
哪些子樹是重複的?
黑魔法訓練營|DP & Greedy
把 DFS 過程畫出來可能會長這樣:
X
X0
\(\vdots\)
\(\vdots\)
不拿
\(\vdots\)
\(\vdots\)
拿
X1
X00
X01
X10
X11
左邊和右邊長的幾乎一模一樣!
差別只在第一個拿不拿。
X
X0
\(\vdots\)
\(\vdots\)
\(\vdots\)
\(\vdots\)
X1
X00
X01
X10
X11
黑魔法訓練營|DP & Greedy
我們的樹可以簡化成這樣:
X
X0
\(\vdots\)
\(\vdots\)
\(\vdots\)
\(\vdots\)
拿或不拿
X1
X00
X01
X10
X11
黑魔法訓練營|DP & Greedy
再簡化:
X
XY
\(\vdots\)
\(\vdots\)
拿或不拿
XY0
XY1
拿或不拿
黑魔法訓練營|DP & Greedy
再再簡化:
X
XY
\(\vdots\)
拿或不拿
XYZ
拿或不拿
拿或不拿
黑魔法訓練營|DP & Greedy
第一個拿不拿不會影響到其他人,
所以可以先計算後面的最佳答案,
再來決定第一個要不要拿。
好像有點複雜過頭了?
別急!先看後面的題目吧。
這樣可以讓樹的大小從 \(O(2 ^ N)\) 變成 \(O(N)\),
時間複雜度也降為 \(O(N)\) !
黑魔法訓練營|DP & Greedy
回到暖身題 3
一樣用暴力無腦的作法。
枚舉 \(2 ^ N\) 種每一個元素要不要拿的情況,
驗證拿法有沒有符合「不連續」的條件,
再來算總和。
時間複雜度還是 \(O(N \cdot 2 ^ N)\) 。
黑魔法訓練營|DP & Greedy
把 DFS 過程畫出來可能會長這樣:
0
00
\(\vdots\)
\(\vdots\)
不拿
\(\vdots\)
1
\(\vdots\)
\(\vdots\)
拿
01
10
11
000
001
010
011
100
101
有些子樹被破壞掉了!
要怎麼簡化?
黑魔法訓練營|DP & Greedy
不管拿或不拿都可以用 X0 的子樹:
0
X0
\(\vdots\)
\(\vdots\)
不拿
\(\vdots\)
1
拿
01
11
X00
X01
010
011
黑魔法訓練營|DP & Greedy
只有不拿可以用 X1 的子樹:
0
X0
\(\vdots\)
\(\vdots\)
不拿
\(\vdots\)
1
拿
X1
X00
X01
X10
X11
黑魔法訓練營|DP & Greedy
再簡化:
0
X0
\(\vdots\)
\(\vdots\)
不拿
1
拿
X1
XY0
XY1
黑魔法訓練營|DP & Greedy
第 \(i\) 個人要不要拿,
其實只在乎第 \(i + 1\) 個人有沒有拿,
再更後面的一概不管。
好像有點複雜,寫成程式看看吧。
把子樹全部並起來,
變成兩棵 X0 和 X1 ,
節點和複雜度都降為 \(O(N)\)!
黑魔法訓練營|DP & Greedy
typedef long long ll;
ll solve(int N, vector<int>& A) {
// dp[i][0/1]: 第 i 個拿 / 不拿
vector dp(N + 1, vector<ll>(2, 0));
for (int i = 1; i <= N; ++i) {
dp[i][0] = max(dp[i-1][0], dp[i-1][1]);
dp[i][1] = dp[i-1][0] + A[i];
}
return max(dp[N][0], dp[N][1]);
}
我們倒過來做,從 \(i = 1\) 開始決定:
用 dp 陣列紀錄子樹答案
不拿的話可以用 X0 或 X1
拿的話只能用 X0
黑魔法訓練營|DP & Greedy
黑魔法訓練營|DP & Greedy
DP 是什麼?
DP 全名動態規劃 Dynamic Programming ,是一個名字和內容不太相關的技巧。
DP 的精神很簡單,
就是算過的東西不要重複算。
DP 可以優化暴力算法的複雜度,
也是因為省略了重複的部份。
一般使用 DP 的時候其實不會把樹畫出來,
思考的過程大概如下:
1. 可以先想暴力作法。
2. 按照暴力作法或自定義的順序計算,
思考計算時會在乎什麼資訊。
以暖身題 3 來說,我們會在乎
「上一項有沒有被選」。
我們將這些必要的資訊稱之為狀態。
黑魔法訓練營|DP & Greedy
3. 利用已經算好的狀態
計算還沒算好的狀態。
這個過程被稱為狀態間的轉移。
4. 設定一開始什麼都沒有的狀態。
又稱為基底狀態。
一般使用 DP 的時候其實不會把樹畫出來,
思考的過程大概如下:
黑魔法訓練營|DP & Greedy
黑魔法訓練營|DP & Greedy
給定一個由 \(0 \sim 99\) 之間的數組成、高度為 \(n\) 的直角三角形,由頂端開始走,每次只能往下或往右下的格子走,請問到達底部時,所經的所有數字和最大可以是多少?
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
例:左圖答案為 \(7 + 3 + 8 + 7 + 5 = 30\) 。
時空複雜度要求:\(O(N^2)\) / \(O(N^2)\) 。
這個可以點!
黑魔法訓練營|DP & Greedy
因為只能往下走,我們不妨考慮由上到下計算答案。
考慮計算走到第 \(i\) 列第 \(j\) 個的最大路徑,
計算時並不在乎上面走過來的路徑長什麼樣子,
重要的只有在第 \(i - 1\) 行時的位置。
這樣狀態和轉移就呼之欲出了!
狀態可以用兩個數字 \((i, j)\) 表示,
代表走到第 \(i\) 列第 \(j\) 個的答案,
而 \((i, j)\) 可以從 \((i-1, j)\) 和 \((i-1, j-1)\)轉移!
黑魔法訓練營|DP & Greedy
\((i, j)\)
黑魔法訓練營|DP & Greedy
\((i, j)\)
\((i-1, j-1)\)
\((i-1, j)\)
黑魔法訓練營|DP & Greedy
\((i, j)\)
\((i-1, j-1)\)
\((i-1, j)\)
黑魔法訓練營|DP & Greedy
int solve(int n, vector<vector<int>>& a) {
vector<vector<int>> dp(n + 1,
vector<int>(n + 1, 0));
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= i; ++j)
dp[i][j] = max(dp[i-1][j], dp[i-1][j-1])
+ a[i][j];
int ans = 0;
for (int i = 1; i <= n; ++i)
ans = max(ans, dp[n][i]);
return ans;
}
\(dp(i, j)\)
由上而下、由左而右計算
轉移
取最大值為答案
黑魔法訓練營|DP & Greedy
至於基底狀態呢?
通常會設計一個讓程式不需要
特別判斷邊界的狀態。
例如上面的程式碼,
只要把第 \(0\) 行和第 \(0\) 列設成 \(0\)
就不會被算到不該出現的答案了。
黑魔法訓練營|DP & Greedy
有 \(n\) 個物品,第 \(i\) 個物品的重量是 \(w_i\) ,價值為 \(v_i\) ,每個物品不可分割。現在你有一個能承受最多 \(x\) 單位重量的背包,請問這個背包最多可以裝下多少價值的物品?
n = 3
x = 10
(wi, ci):
(3, 100)
(2, 10)
(8, 10)
例:左圖答案為 \(10 + 10 = 20\) 。
時空複雜度要求:\(O(nx)\) / \(O(x)\) 。
題目有點不太一樣,但不影響
黑魔法訓練營|DP & Greedy
暴力的作法是依序考慮每個東西要不要加。
我們可以考慮依序計算每一個物品
加入背包後的答案。
加入第 \(i\) 個物品時,
我們只在乎背包的剩餘容量是否足夠,
而不在乎先前加入物品的方式。
因此我們可以把狀態設定為 \((i, j)\) ,
代表加入了第 \(i\) 個物品時,
剩餘容量為 \(j\) 所能裝下的最大價值。
黑魔法訓練營|DP & Greedy
轉移也很簡單:
如果不加入第 \(i\) 個物品,
則 \(dp(i, j) = dp(i - 1, j)\) ,
否則 \(dp(i, j) = dp(i - 1, j + w_i) + v_i\) ,
兩邊取最大即得答案。
int solve(int n, int x, vector<int>& w, vector<int>& v) {
vector<vector<int>> dp(n+1, vector<int>(x+1, 0));
for (int i = 1; i <= n; ++i)
for (int j = 0; j + w[i] <= x; ++j)
dp[i][j] = max(dp[i-1][j], dp[i-1][j+w[i]] + v[i]);
return dp[n][0];
}
但是這樣的空間複雜度是 \(O(nx)\) 的!
黑魔法訓練營|DP & Greedy
壓縮空間的方式會用到一個叫做滾動的技巧。
int solve(int n, int x, vector<int>& w, vector<int>& v) {
vector<int> dp(x+1, 0), sc(x+1, 0);
for (int i = 1; i <= n; ++i) {
for (int j = 0; j + w[i] <= x; ++j)
dp[j] = max(sc[j], sc[j+w[i]] + v[i]);
swap(dp, sc);
}
return dp[n][0];
}
這樣的空間複雜度就是 \(O(x)\) 的!
記得 vector swap 是 \(O(1)\) 的喔。
在計算第 \(dp_i\) 個物品的時候,
其實只需要 \(dp_{i-1}\),
\(1 \sim i - 2\) 和 \(i + 1 \sim n\) 都可以不用開!
黑魔法訓練營|DP & Greedy
其實可以再壓,只要開一個陣列就好了。
int solve(int n, int x, vector<int>& w, vector<int>& v) {
vector<int> dp(x+1, 0);
for (int i = 1; i <= n; ++i) {
for (int j = 0; j + w[i] <= x; ++j)
dp[j] = max(dp[j], dp[j+w[i]] + v[i]);
}
return dp[n][0];
}
轉移過程如下圖,不會重複取到同一個物品:
黑魔法訓練營|DP & Greedy
有 \(n\) 種物品,第 \(i\) 個物品的重量是 \(w_i\) ,價值為 \(v_i\) ,每個物品不可分割,但是每種都有無限個。現在你有一個能承受最多 \(x\) 單位重量的背包,請問這個背包最多可以裝下多少價值的物品?
n = 3
x = 10
(wi, ci):
(3, 100)
(2, 10)
(8, 10)
例:左圖答案為 \(100 \cdot 3 = 300\) 。
時空複雜度要求:\(O(nx)\) / \(O(x)\) 。
黑魔法訓練營|DP & Greedy
寫出來跟剛剛差不多:
int solve(int n, int x, vector<int>& w, vector<int>& v) {
vector<int> dp(x+1, 0);
for (int i = 1; i <= n; ++i) {
for (int j = x - w[i]; j >= 0; --j)
dp[j] = max(dp[j], dp[j+w[i]] + v[i]);
}
return dp[n][0];
}
轉移過程如下圖,會重複取到同一個物品:
題目也可以把這兩種轉移出在一起喔!
黑魔法訓練營|DP & Greedy
有 \(n\) 隻史萊姆排成一直線,每隻都是一單位體積,第 \(i\) 隻的戰鬥力為 \(a_i\) 。現在可以按照任意順序將相鄰兩隻合併直到剩下一隻,合併時體積相加;若兩隻的體積合為奇數,新的攻擊力則為原本兩隻的乘積,否則為兩隻的總合。
求最後能產出的史萊姆攻擊力最大為多少?
n = 3 1 2 3
例:左圖答案為 \((1 + 2) \cdot 3 = 9\) 。
時空複雜度要求:\(O(n^3)\) / \(O(n^2)\) 。
黑魔法訓練營|DP & Greedy
最暴力的作法是枚舉最後那隻是由誰合併來的,
並遞迴下去,直到剩下體積為 \(1\) 。
不難發現遞迴時求的是「區間 \([l, r]\) 合併起來
最大的攻擊力是多少」。
轉移的方式題目已經給定了,
現在要處理的剩下計算的順序⋯⋯
其實就按照遞迴的順序也不是不行啦!
黑魔法訓練營|DP & Greedy
雖然程式長的和前面不太一樣,
不過 DP 的精神是一樣的!
int n, a[111];
long long dp[111][111];
long long solve(int l, int r) {
if (l == r) dp[l][r] = a[l];
if (dp[l][r] != -1) return dp[l][r];
for (int m = l; m < r; ++m) {
if ((r - l + 1) % 2 == 1)
dp[l][r] = max(dp[l][r], solve(l, m) * solve(m+1, r));
else
dp[l][r] = max(dp[l][r], solve(l, m) + solve(m+1, r));
}
return dp[l][r];
}
基底狀態
算過不要再算
轉移
已經全部填 \(-1\) 了
黑魔法訓練營|DP & Greedy
只要確保需要被用到的狀態都會先被算完就好,
迴圈的效率較佳!
long long solve() {
for (int i = 1; i <= n; ++i) dp[i][i] = a[i];
for (int r = 2; r <= n; ++r)
for (int l = r - 1; l >= 1; --l)
for (int m = l; m < r; ++m)
if ((r - l + 1) % 2 == 1)
dp[l][r] = max(dp[l][r], dp[l][m] * dp[m+1][r]);
else
dp[l][r] = max(dp[l][r], dp[l][m] + dp[m+1][r]);
return dp[1][n];
}
基底狀態
轉移
黑魔法訓練營|DP & Greedy
接下來要進入填鴨教育模式了。
一般在解 DP 題目時,最難的部份往往是找出狀態並寫出轉移式。
因此,以下的每一題只要作答:
剩下的實作請大家利用時間練習喔!
黑魔法訓練營|DP & Greedy
有 \(n\) 個平台,由下到上第 \(i\) 個平台的水平位置在 \(d_i\) 。
遊戲從第一個平台開始,每次可以往上跳一個或兩個平台,求跳到第 \(n\) 個平台時最小的總水平位移。
黑魔法訓練營|DP & Greedy
狀態:
轉移:
複雜度:
\(dp(i)\) 代表跳到第 \(i\) 個平台的答案
\(dp(i) = \min_{j \in \{1, 2\}}(dp(i-j) + |d_{i-j} - d_i|)\)
\(O(n)\) / \(O(n)\)
給定兩個字串 \(s\) 和 \(t\) ,求兩字串的最長共同子序列。
\(a\) 是 \(b\) 的子序列代表 \(a\) 可以由 \(b\) 刪除任意數量元素後得到。
黑魔法訓練營|DP & Greedy
狀態:
轉移:
複雜度:
\(dp(i, j)\) 代表 \(s[1, i]\) 和 \(t[1, j]\) 的 LCS 長度。
\(dp(i, j) = \max(dp(i-1,j), dp(i,j-1),\)
\(dp(i-1,j-1) + (s_i == t_j))\)
\(O(|s||t|)\) / \(O(|s|+|t|)\)
給一個長度為 \(n\) 的陣列 \(a\) ,求 \(a\) 的所有遞增子序列中最長的長度。
這題有 \(O(n \log n)\) 的作法,有時間再講。
黑魔法訓練營|DP & Greedy
狀態:
轉移:
複雜度:
\(dp(i)\) 代表以第 \(i\) 項為結尾的 LIS 長度
\(dp(i) = \max _ {j < i, a_j < a_i} dp(j) + 1\)
\(O(n^2)\) / \(O(n)\)
有 \(n\) 種物品,第 \(i\) 個物品的重量是 \(w_i\) ,價值為 \(v_i\) ,每個物品可以分割,而且每種都有無限個。現在你有一個能承受最多 \(x\) 單位重量的背包,請問這個背包最多可以裝下多少價值的物品?
黑魔法訓練營|DP & Greedy
狀態:
轉移:
複雜度:
哈哈,拿 \(\frac{v_i}{w_i}\) 最大的就好了。
題目有點不太一樣,但不影響
休息是為了不要走路。
黑魔法訓練營|DP & Greedy
至於哪個要取?
非負整數取了一定不虧,不拿白不拿!
假設我們知道哪個要取,哪個不要取,
那麼我們就可以用 \(O(N)\) 時間計算總和。
實作很簡單就不提供程式碼了。
黑魔法訓練營|DP & Greedy
有一個 \(n\) 單位長的農地,每一單位不是荒地 (#) 就是耕地 (.)
一個稻草人可以保護連續的 \(3\) 單位的農地。
每一單位的耕地都要被至少一個稻草人完整的保護,求最少需要的稻草人數。
...##....##
例:左圖答案為 \(3\) 。
時空複雜度要求:\(O(n)\) / \(O(n)\) 。
黑魔法訓練營|DP & Greedy
以最左邊當第一格,依序往右考慮。
如果 \(i - 1\) 格以前都解決了,
在第 \(i\) 格有一個耕地,
那放一個稻草人在 \([i, i + 2]\) 一定不虧。
(為什麼?)
那就做完了!
實作一樣很簡單。
黑魔法訓練營|DP & Greedy
只要能夠證明「這樣做一定不虧」,
那麼 Greedy 的作法也就隨之而來了。
注意,不需要證明「這樣做一定賺」,
只要不虧就好了。
用 Greedy 的作法如果沒有想清楚,
往往會導致假解,
請記得大膽唬爛小心上傳喔。
黑魔法訓練營|DP & Greedy
什麼狀況會虧?
如果從最大值開始取,
取到不能再取或非負整數取完,
這樣做會對嗎?
反例:\([3, 4, 3, 4, 3]\) 。
這題其實是有 Greedy 作法的,但是需要搭配進階的技巧,就先不提了。
黑魔法訓練營|DP & Greedy
黑魔法訓練營|DP & Greedy
有 \(n\) 個一維的線段 \([l_i, r_i]\) ,求最多可以選幾個,使得被選的線段兩兩不相交。
時空複雜度要求:\(O(n \log n)\) / \(O(n)\)
例:右圖答案為 \(2\) ,且有多組解。
黑魔法訓練營|DP & Greedy
從最短的開始取?
假解。
從左到右取?
這樣的線段要怎麼比較左右順序?
有多個可以取要取哪個?
黑魔法訓練營|DP & Greedy
稍微套用一點 DP 的想法。
從左邊到右邊計算,每次判斷可不可以拿
都只會在乎目前拿的人右界最大到哪。
假設固定目前右界後,
該怎麼決定下一個拿誰呢?
黑魔法訓練營|DP & Greedy
剛才說到重要的資訊是目前最右邊的右界。
可以發現,右界越左邊,可以選的線段一定
不會比較少。
所以我們的作法就出來了:
將線段按照右界排序,
每次選可以選的線段當中,右界最左邊的!
黑魔法訓練營|DP & Greedy
typedef pair<int, int> pii;
int solve(int n, vector<pii>& events) {
sort(begin(events), end(events),
[](pii a, pii b) {
return a.second < b.second;
});
int ans = 0, cur_r = 0;
for (int i = 0; i < n; ++i)
if (events[i].first >= cur_r) {
cur_r = events[i].second;
ans += 1;
}
return ans;
}
按照右界排序
能拿就拿
黑魔法訓練營|DP & Greedy
有 \(n\) 個正整數 \(a_i\) ,每次可以花費 \(a_i + a_j\) 合併 \(i\) 和 \(j\) ,求將所有數字合併完的最小代價。
時空複雜度要求:\(O(n \log n)\) / \(O(n)\)。
類題
例:\([3, 2, 3, 2] \rightarrow [3, 3, 4] \rightarrow [6, 4] \rightarrow [10]\) ,花費 \(4 + 6 + 10 = 20\) 。
黑魔法訓練營|DP & Greedy
不論如何都一定要合併 \(n - 1\) 次。
如果每次都挑最小的出來合併呢?
試著證明這樣做不虧!
用反證法!
令三個數字 \(x \le y \le z\) ,
若先合併了 \(x\) 和 \(z\) ,
則未來 \(z\) 一定還要再多合併一次。
若選擇先合併 \(y\) ,
則多出來的那一次花費會減少 \(z - y \ge 0\) ,
花費一定不比先合併 \(z\) 高。
黑魔法訓練營|DP & Greedy
有 \(n\) 個人要吃晚餐,第 \(i\) 個人要點的餐點需要 \(c_i\) 的製作時間,並且吃完要花 \(e_i\) 單位時間。同一時間只能製作一份餐點,求最後一個吃完的人最早可以吃完。
時空複雜度要求:\(O(n \log n)\) / \(O(n)\)。
c e
1 1
2 5
3 3
例:左方三個人依序製作,吃完時間分別為
\([2, 8, 9]\) ,答案為 \(9\) 。
黑魔法訓練營|DP & Greedy
想辦法訂出一個吃飯的順序。
排序其實是一種數學歸納法?
比較看看編號 \(i\) 和 \(j\) ,看誰先晚餐。
\(i\) 先 \(\rightarrow \max(c_i + e_i, c_i + c_j + e_j)\)
\(j\) 先 \(\rightarrow \max(c_j + e_j, c_j + c_i + e_i)\)
如果 \(i\) 吃超久 (\(e_i \ge c_j + e_j\)) ,那 \(i\) 先一定不虧。
黑魔法訓練營|DP & Greedy
\(i\) 先 \(\rightarrow \max(c_i + e_i, c_i + c_j + e_j)\)
\(j\) 先 \(\rightarrow \max(c_j + e_j, c_j + c_i + e_i)\)
如果 \(e_i < c_j + e_j \cap e_j < c_i + e_i\) ,
則由後面那項決定最後吃完的是誰。
可以發現 \(e_i\) 比較大的人先一定不虧。
綜上所述,只要按照 \(e_i\) 由大到小排序,
一一計算吃完時間取最大值即可!
黑魔法訓練營|DP & Greedy
接下來是證明題時間!
請對接下來的題目證明作法正確性,
或是找出反例。
證明作法正確性在使用 Greedy 時非常重要,
不要像講師一樣在 APCS 假解
錯過滿分機會⋯⋯
黑魔法訓練營|DP & Greedy
[No Judge]
某國硬幣有 \(N\) 種面額 \(a_1, a_2, \cdots, a_N\)
現在給你要用這些幣值湊出 \(X\) 元,
求最少需要幾個硬幣。
黑魔法訓練營|DP & Greedy
有一長度為 \(n\) 的整數陣列 \(a\) ,請選擇不重疊的 \(k\) 段子區間,使得每一段內都沒有重複的數字。求這些子區間最大的覆蓋量。
複雜度要求:\(O(nk)\) / \(O(nk)\)
黑魔法訓練營|DP & Greedy
有一長度為 \(n\) 個線段 \([L_i, R_i)\) ,現在可以移除 \(K\) 個線段,要使得數線上任一點被覆蓋的線段數最大值最小。求此最大值最小可以是多少。
時空複雜度要求:\(O(n \log ^ 2 n)\) / \(O(n)\)
黑魔法訓練營|DP & Greedy
黑魔法訓練營|DP & Greedy
DP 精神:算過的不要重複算,
定順序、狀態、轉移,計算答案。
Greedy 精神:不拿白不拿,
證明作法不虧。
這兩個技巧的題目變化多端,
建議靠刷題累積經驗,
知道越多梗一定不虧。
黑魔法訓練營|DP & Greedy
黑魔法訓練營|DP & Greedy
By FHVirus
北一女中 2022 黑魔法訓練營 DP & Greedy 課程簡報