DP & Greedy

 by FHVirus

黑魔法訓練營

  • 姓名:王 褕ㄩˊ 立
  • 常用 ID :FHVirus
  • Discord:FHVirus#4823
  • 我弱
  • 為了向 hhhhaura 學習 DP & Greedy
    所以來當 DP & Greedy 講師

講師自我介紹

黑魔法訓練營|DP & Greedy

  • 暖身題
  • DP 入門
  • 更多 DP
  • Greedy 入門
  • 更多 Greedy

課程大綱

如果已經會上課內容的話,可以寫看看下一頁的題單。

 

有問題隨時都可以發問。

雖然預期是沒有人會想要跟講師講話

 

講義藏有彩蛋,

無聊可以找找看 ><

黑魔法訓練營|DP & Greedy

有空可以寫寫看各個講義裡的例題和習題,也可以挑戰看看最下面的比賽喔。

題單與學習資源

黑魔法訓練營|DP & Greedy

暖身題

黑魔法訓練營|DP & Greedy

暖身題 1

給定一個長度為 \(N\) 的非負整數序列 \(A\) ,

求 \(A\) 的最大子序列和。

很水對吧。

黑魔法訓練營|DP & Greedy

時空複雜度要求:\(O(N)\) / \(O(N)\)

暖身題 2

黑魔法訓練營|DP & Greedy

給定一個長度為 \(N\) 的整數序列 \(A\) ,

求 \(A\) 的最大子序列和。

還是很水對吧。

時空複雜度要求:\(O(N)\) / \(O(N)\)

暖身題 3

給定一個長度為 \(N\) 的整數序列 \(A\) ,

求 \(A\) 的最大不連續子序列和。

好像不那麼水了?

黑魔法訓練營|DP & Greedy

不連續的意思是:對於所有 \(1 \le i < N\) 的 \(i\) ,

\(i\) 和 \(i+1\) 只能選一個。

時空複雜度要求:\(O(N)\) / \(O(N)\)

DP 入門

黑魔法訓練營|DP & Greedy

DP 入門 1

不,不是那個。

更笨一點

枚舉 \(2 ^ N\) 種每一個序列元素要不要拿的情況,

時間複雜度 \(O(N \cdot 2 ^ N)\) 。

假設我們知道哪個要取,哪個不要取,

那麼我們就可以用 \(O(N)\) 時間計算總和。

黑魔法訓練營|DP & Greedy

DP 入門 1

把 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 入門 1

最暴力無腦的作法複雜度當然很差。

如果要改善複雜度的話,

算過的東西不要重複算。

哪些是被重複算到的東西呢?

哪些子樹是重複的?

黑魔法訓練營|DP & Greedy

DP 入門 1

把 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

DP 入門 1

我們的樹可以簡化成這樣:

X
X0

\(\vdots\)

\(\vdots\)

\(\vdots\)

\(\vdots\)

拿或不拿

X1
X00
X01
X10
X11

黑魔法訓練營|DP & Greedy

DP 入門 1

再簡化:

X
XY

\(\vdots\)

\(\vdots\)

拿或不拿

XY0
XY1

拿或不拿

黑魔法訓練營|DP & Greedy

DP 入門 1

再再簡化:

X
XY

\(\vdots\)

拿或不拿

XYZ

拿或不拿

拿或不拿

黑魔法訓練營|DP & Greedy

DP 入門 1

第一個拿不拿不會影響到其他人,

所以可以先計算後面的最佳答案,

再來決定第一個要不要拿。

好像有點複雜過頭了?

別急!先看後面的題目吧。

這樣可以讓樹的大小從 \(O(2 ^ N)\) 變成 \(O(N)\),

時間複雜度也降為 \(O(N)\) !

黑魔法訓練營|DP & Greedy

DP 入門 2

回到暖身題 3

一樣用暴力無腦的作法。

枚舉 \(2 ^ N\) 種每一個元素要不要拿的情況,

驗證拿法有沒有符合「不連續」的條件,

再來算總和。

時間複雜度還是 \(O(N \cdot 2 ^ N)\) 。

黑魔法訓練營|DP & Greedy

DP 入門 2

把 DFS 過程畫出來可能會長這樣:

0
00

\(\vdots\)

\(\vdots\)

不拿

\(\vdots\)

1

\(\vdots\)

\(\vdots\)

01
10
11
000
001
010
011
100
101

有些子樹被破壞掉了!

要怎麼簡化?

黑魔法訓練營|DP & Greedy

DP 入門 2

不管拿或不拿都可以用 X0 的子樹:

0
X0

\(\vdots\)

\(\vdots\)

不拿

\(\vdots\)

1

01
11
X00
X01
010
011

黑魔法訓練營|DP & Greedy

DP 入門 2

只有不拿可以用 X1 的子樹:

0
X0

\(\vdots\)

\(\vdots\)

不拿

\(\vdots\)

1

X1
X00
X01
X10
X11

黑魔法訓練營|DP & Greedy

DP 入門 2

再簡化:

0
X0

\(\vdots\)

\(\vdots\)

不拿

1

X1
XY0
XY1

黑魔法訓練營|DP & Greedy

DP 入門 2

第 \(i\) 個人要不要拿,

其實只在乎第 \(i + 1\) 個人有沒有拿,

再更後面的一概不管。

好像有點複雜,寫成程式看看吧。

把子樹全部並起來,

變成兩棵 X0 和 X1 ,

節點和複雜度都降為 \(O(N)\)!

黑魔法訓練營|DP & Greedy

DP 入門 2

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 入門 3

黑魔法訓練營|DP & Greedy

DP 是什麼?

DP 全名動態規劃 Dynamic Programming ,是一個名字和內容不太相關的技巧。

DP 的精神很簡單,

就是算過的東西不要重複算

DP 可以優化暴力算法的複雜度,

也是因為省略了重複的部份。

DP 入門 3

 一般使用 DP 的時候其實不會把樹畫出來,

思考的過程大概如下:

1. 可以先想暴力作法。

2. 按照暴力作法或自定義的順序計算,

思考計算時會在乎什麼資訊。

以暖身題 3 來說,我們會在乎
「上一項有沒有被選」。
我們將這些必要的資訊稱之為狀態

黑魔法訓練營|DP & Greedy

DP 入門 3

3. 利用已經算好的狀態

計算還沒算好的狀態

這個過程被稱為狀態間的轉移

4. 設定一開始什麼都沒有的狀態

又稱為基底狀態

 一般使用 DP 的時候其實不會把樹畫出來,

思考的過程大概如下:

黑魔法訓練營|DP & Greedy

更多 DP

黑魔法訓練營|DP & Greedy

更多 DP 1

三角旅行

給定一個由 \(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

更多 DP 1

因為只能往下走,我們不妨考慮由上到下計算答案。

考慮計算走到第 \(i\) 列第 \(j\) 個的最大路徑,

計算時並不在乎上面走過來的路徑長什麼樣子,

重要的只有在第 \(i - 1\) 行時的位置。

這樣狀態和轉移就呼之欲出了!

狀態可以用兩個數字 \((i, j)\) 表示,

代表走到第 \(i\) 列第 \(j\) 個的答案,

而 \((i, j)\) 可以從 \((i-1, j)\) 和 \((i-1, j-1)\)轉移!

黑魔法訓練營|DP & Greedy

更多 DP 1

\((i, j)\)

狀態

黑魔法訓練營|DP & Greedy

更多 DP 1

\((i, j)\)

\((i-1, j-1)\)

\((i-1, j)\)

狀態和來源

黑魔法訓練營|DP & Greedy

更多 DP 1

\((i, j)\)

\((i-1, j-1)\)

\((i-1, j)\)

狀態和來源

轉移

黑魔法訓練營|DP & Greedy

更多 DP 1

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

更多 DP 1

至於基底狀態呢?

通常會設計一個讓程式不需要

特別判斷邊界的狀態。

例如上面的程式碼,

只要把第 \(0\) 行和第 \(0\) 列設成 \(0\)

就不會被算到不該出現的答案了。

黑魔法訓練營|DP & Greedy

更多 DP 2

0-1 背包問題

有 \(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

更多 DP 2

暴力的作法是依序考慮每個東西要不要加。

我們可以考慮依序計算每一個物品

加入背包後的答案。

加入第 \(i\) 個物品時,

我們只在乎背包的剩餘容量是否足夠,

而不在乎先前加入物品的方式。

因此我們可以把狀態設定為 \((i, j)\) ,

代表加入了第 \(i\) 個物品時,

剩餘容量為 \(j\) 所能裝下的最大價值。

黑魔法訓練營|DP & Greedy

更多 DP 2

轉移也很簡單:

如果不加入第 \(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

更多 DP 2

壓縮空間的方式會用到一個叫做滾動的技巧。

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

更多 DP 2

其實可以再壓,只要開一個陣列就好了。

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

更多 DP 2

無限背包問題

有 \(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

更多 DP 2

寫出來跟剛剛差不多:

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

更多 DP 3

好強的史萊姆

有 \(n\) 隻史萊姆排成一直線,每隻都是一單位體積,第 \(i\) 隻的戰鬥力為 \(a_i\) 。現在可以按照任意順序將相鄰兩隻合併直到剩下一隻,合併時體積相加;若兩隻的體積合為奇數,新的攻擊力則為原本兩隻的乘積,否則為兩隻的總合。

求最後能產出的史萊姆攻擊力最大為多少?

n = 3

1 2 3

例:左圖答案為 \((1 + 2) \cdot 3 = 9\) 。

時空複雜度要求:\(O(n^3)\) / \(O(n^2)\) 。

黑魔法訓練營|DP & Greedy

更多 DP 3

最暴力的作法是枚舉最後那隻是由誰合併來的,

並遞迴下去,直到剩下體積為 \(1\) 。

不難發現遞迴時求的是「區間 \([l, r]\) 合併起來

最大的攻擊力是多少」。

轉移的方式題目已經給定了,

現在要處理的剩下計算的順序⋯⋯

其實就按照遞迴的順序也不是不行啦!

黑魔法訓練營|DP & Greedy

更多 DP 3

雖然程式長的和前面不太一樣,

不過 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

更多 DP 3

只要確保需要被用到的狀態都會先被算完就好,

迴圈的效率較佳!

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 題目時,最難的部份往往是找出狀態並寫出轉移式。

因此,以下的每一題只要作答:

  • 狀態
  • 轉移
  • 時間複雜度

剩下的實作請大家利用時間練習喔!

黑魔法訓練營|DP & Greedy

更多 DP 4

Jumping Up

有 \(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)\)

更多 DP 5

Longest Common Subsequence

給定兩個字串 \(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|)\)

更多 DP 6

Longest Increasing Subsequence

給一個長度為 \(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)\)

更多 DP 7

無限背包問題 2

有 \(n\) 種物品,第 \(i\) 個物品的重量是 \(w_i\) ,價值為 \(v_i\) ,每個物品可以分割,而且每種都有無限個。現在你有一個能承受最多 \(x\) 單位重量的背包,請問這個背包最多可以裝下多少價值的物品?

黑魔法訓練營|DP & Greedy

狀態:

轉移:

複雜度:

哈哈,拿 \(\frac{v_i}{w_i}\) 最大的就好了。

題目有點不太一樣,但不影響

休息時間

休息是為了不要走路。

Greedy 入門

黑魔法訓練營|DP & Greedy

Greedy 入門 1

至於哪個要取?

非負整數取了一定不虧,不拿白不拿!

假設我們知道哪個要取,哪個不要取,

那麼我們就可以用 \(O(N)\) 時間計算總和。

實作很簡單就不提供程式碼了。

黑魔法訓練營|DP & Greedy

Greedy 入門 2

Scarecrow

有一個 \(n\) 單位長的農地,每一單位不是荒地 (#) 就是耕地 (.)

一個稻草人可以保護連續的 \(3\) 單位的農地。

每一單位的耕地都要被至少一個稻草人完整的保護,求最少需要的稻草人數。

...##....##

例:左圖答案為 \(3\) 。

時空複雜度要求:\(O(n)\) / \(O(n)\) 。

黑魔法訓練營|DP & Greedy

Greedy 入門 2

以最左邊當第一格,依序往右考慮。

如果 \(i - 1\) 格以前都解決了,

在第 \(i\) 格有一個耕地,

那放一個稻草人在 \([i, i + 2]\) 一定不虧。

(為什麼?)

那就做完了!

實作一樣很簡單。

黑魔法訓練營|DP & Greedy

Greedy 入門

只要能夠證明「這樣做一定不虧」,

那麼 Greedy 的作法也就隨之而來了。

注意,不需要證明「這樣做一定賺」,

只要不虧就好了。

用  Greedy 的作法如果沒有想清楚,

往往會導致假解,

請記得大膽唬爛小心上傳喔。

黑魔法訓練營|DP & Greedy

Greedy 入門 3

什麼狀況會虧?

如果從最大值開始取,

取到不能再取或非負整數取完,

這樣做會對嗎?

反例:\([3, 4, 3, 4, 3]\) 。

這題其實是有 Greedy 作法的,但是需要搭配進階的技巧,就先不提了。

黑魔法訓練營|DP & Greedy

更多 Greedy

黑魔法訓練營|DP & Greedy

更多 Greedy 1

最大線段獨立集

有 \(n\) 個一維的線段 \([l_i, r_i]\) ,求最多可以選幾個,使得被選的線段兩兩不相交。

時空複雜度要求:\(O(n \log n)\) / \(O(n)\)

例:右圖答案為 \(2\) ,且有多組解。

黑魔法訓練營|DP & Greedy

更多 Greedy 1

從最短的開始取?

假解。

從左到右取?

這樣的線段要怎麼比較左右順序?

有多個可以取要取哪個?

黑魔法訓練營|DP & Greedy

更多 Greedy 1

稍微套用一點 DP 的想法。

從左邊到右邊計算,每次判斷可不可以拿

都只會在乎目前拿的人右界最大到哪。

假設固定目前右界後,

該怎麼決定下一個拿誰呢?

黑魔法訓練營|DP & Greedy

更多 Greedy 1

剛才說到重要的資訊是目前最右邊的右界。

可以發現,右界越左邊,可以選的線段一定

不會比較少

所以我們的作法就出來了:

將線段按照右界排序,

每次選可以選的線段當中,右界最左邊的!

黑魔法訓練營|DP & Greedy

更多 Greedy 1

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

更多 Greedy 2

蘿莉切割問題

有 \(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

更多 Greedy 2

不論如何都一定要合併 \(n - 1\) 次。

如果每次都挑最小的出來合併呢?

試著證明這樣做不虧!

用反證法!

令三個數字 \(x \le y \le z\) ,

若先合併了 \(x\) 和 \(z\) ,

則未來 \(z\) 一定還要再多合併一次。

若選擇先合併 \(y\) ,

則多出來的那一次花費會減少 \(z - y \ge 0\) ,

花費一定不比先合併 \(z\) 高。

黑魔法訓練營|DP & Greedy

更多 Greedy 3

誰先晚餐

有 \(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

更多 Greedy 3

想辦法訂出一個吃飯的順序。

排序其實是一種數學歸納法?

比較看看編號 \(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

更多 Greedy 3

\(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

接下來是證明題時間!

請對接下來的題目證明作法正確性,

或是找出反例。

證明作法正確性在使用 Greedy 時非常重要,

不要像講師一樣在 APCS 假解

錯過滿分機會⋯⋯

黑魔法訓練營|DP & Greedy

更多 Greedy 4

找零問題

[No Judge]

某國硬幣有 \(N\) 種面額 \(a_1, a_2, \cdots, a_N\)

現在給你要用這些幣值湊出 \(X\) 元,

求最少需要幾個硬幣。

黑魔法訓練營|DP & Greedy

  • 可以 Greedy 嗎?試著構反例。
  • 證明臺灣的流通貨幣(含二十、兩百元)可以用 Greedy
  • 可以 Greedy 的充要條件?

更多 Greedy 5

美食博覽會

有一長度為 \(n\) 的整數陣列 \(a\) ,請選擇不重疊的 \(k\) 段子區間,使得每一段內都沒有重複的數字。求這些子區間最大的覆蓋量。

複雜度要求:\(O(nk)\) / \(O(nk)\)

黑魔法訓練營|DP & Greedy

  • \(k = 1\) 可以 Greedy 嗎?
  • \(k > 1\) 可以 Greedy 嗎?
  • 反例:\([4 3 1 2 3 4 5 3 1]\) ,講師考 APCS 用 Greedy 假解 qwq
  • DP 要怎麼做?

更多 Greedy 6

隕石

有一長度為 \(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 課程簡報

  • 1,107