Greedy, DQ, DP

9/22 校隊培訓

FHVirus

References & 題單

  • 03t 講義 https://slides.com/tim25871014/dp_dq
  • 8e7 講義 https://slides.com/justinlai2003/dq-dp
  • yeshello 講義 https://slides.com/nameno/deck
  • ATC DP contest https://atcoder.jp/contests/dp/tasks
  • baluteshih @1階 reurl.cc/GdNN1x

聲明

這堂課的內容主要是解題的想法、一般性的概念(尤其是DP),

相關的題目變化多端,請多多做題練習。

填表單時間

Greedy

不拿白不拿

國小生都寫的出來(不是嘴砲)

Greedy 的精髓

  • 找到一個方法,證明這樣做一定不會比較差
  • 照著做
  • 上面那題?
  • 小提示:正著很難做的話,反著做
  • Huffman Encoding,TIOJ 上有複數題
  • 有不用 Priority Queue 的作法(要觀察)

線段相交問題

有 \(N\) 個線段 \([l_i, r_i]\),

求最多能選幾個線段

使得他們互不相交

  • 想辦法把對稱的東西變得不對稱
  • 證明你的作法不會比較差
    注意,不是比較好!
  • 對於假解想辦法構造反例

有 \(N \le 10^5\) 個人站成一排,每個人左右手各拿一張牌 \(a_i, b_i\),

每個人的分數為「前面所有人的 \(a\) 乘積除以自己的 \(b\)」。

已知第一個人固定且不計分,

求一種排序,使得所有人分數最大值最小。

  • 排序遞移律&數歸?
  • 證明你的 cmp / operator< 不會比較差
    注意,不是比較好!
  • 丟這題是因為其他你們大概都聽好幾遍了

你有 \(10^9\) 單位的時間以及 \(N \le 10^5\) 份工作。

每一份工作有一個截止時間 \(D_i \le 10^9\)和一個報酬 \(P_i\)。

每一單位時間你可以做一份工作,求最大獲利。

  • 可以反悔?

找錢問題

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

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

求最少需要幾個硬幣。

  • 可以 Greedy 嗎?試著構反例
  • 證明精靈國的流通貨幣可以 Greedy
  • 找出可以 Greedy 的情況

我懶的放題敘

  • 可以 Greedy 嗎?試著構反例
  • 什麼情況下可以 Greedy?
  • 講師 355  WA 這題對不起
  • 不能 Greedy 怎麼辦?等等就知道

我懶的放題敘

  • 難題
  • 休息|實做|想這題

DQ

分而治之

大事化小,小事化無

  • 定義問題形式
  • 分成子問題
  • 解決子問題
  • 合併子問題
  • 注意時間複雜度!

使用時機

  • 可以被分成子問題(廢話)
    反例:求第 \(k\) 小值
  • 最佳解要可以從子問題的最佳解得到
    又被稱為有「最優子結構」
  • 不能有迴圈!
    子問題的子問題的……下去回到自己的話會爛掉
    但也許可以高斯消去?

時間複雜度?

  • 畫圖!
  • 長的好像線段樹喔!
  • Master Theorem 也是從畫圖開始

有興趣自己看

  • TIOJ 1500 的測資超爛
  • 休息|做題|看動漫

DP

分而治之+?

大事化小,小事化無+?

  • 定義問題形式
  • 分成子問題
  • 解決子問題
  • 紀錄子問題答案
  • 合併子問題
  • 好像一模一樣?

DQ + Memoization

當子問題重複,可以紀錄子問題答案,避免重複計算
時間複雜度也會變好!

(不會重複的話就 DQ 就好)

定義問題形式

DP 的問題形式,也就是 DP 狀態

要先定好狀態(想清楚要問什麼)才能找答案

例如費氏數列,狀態 \(dp_i\)  代表第 \(i\) 項

 

這步驟通常是最難的(知道要問什麼問題)
有點玄學有點通靈
多做題也許有用

分成子問題

DP 的子問題,也就是 DP 轉移來源

代表「現在這個問題的答案可能是從哪個子問題來的」

在最佳化問題中,來源不一定單一

例如費氏數列,狀態 \(dp_i\)  的來源為 \(dp_{i-1}, dp_{i-2}\)

 

要特別小心不能再分下去的狀態,也就是邊界狀態

例如費氏數列的邊界狀態就是 \(dp_0 = dp_1 = 1\)

合併子問題

DP 的子問題合併,也稱為 進行轉移

代表「從子問題得到現在狀態的答案」

在最佳化問題中,來源不一定單一

例如費氏數列,轉移就是 \(dp_i = dp_{i-1} +dp_{i-2}\)

 

轉移通常可以寫成式子表示

DP 優化也多從式子出發

強烈建議想題的時候把式子寫出來

 額……紀錄呢?

DP 的問題答案紀錄,也會被稱為 DP 表格

通常開個陣列紀錄之類的

例如費氏數列要求到第 \(N\) 項就開長度為 \(N\) 的陣列

 

也有人會用 DFS + unordered_map

但效率奇差,不建議使用
除非你是要驗題或是唬爛

盱衡上述,DP 做的事情是……

  • 定好問題狀態
  • 想好問題轉移
  • 打好邊界基礎

使用時機

  • 可以被分成子問題(廢話)
    反例:求第 \(k\) 小值
  • 最佳解要可以從子問題的最佳解得到
    又被稱為有「最優子結構」
  • 子問題重複(廢話)
    不然就不用紀錄了嘛
  • 不能有迴圈!
    子問題的子問題的……下去回到自己的話會爛掉
    但也許可以高斯消去?
  • 學 DP 就是一個做題的過程
  • 每人給一(組)題,十五分鐘後上來講做法
    不要太有壓力  ><
    解不出來也可以講思路就好 OW0)b

刷題,刷題,刷題

不是因為很重要所以講三次
是因為學低批就真的是這樣

實做流派

你爽就好,但某些題目不同作法難度會不同

Bottom-Up
(Push)

Bottom-Up
(Pull)

Top-Down
(DFS style)
常數稍大

小技巧:滾動

當 DP 長的像這樣:\(dp_{i, [\cdots]} = f(dp_{i-1, [\cdots]})\)

也就是所有的 \(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 \times 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 一次!

小技巧:滾動

滾動好處多多!

  • 節省空間
    有些題目會卡掉沒有滾動的
  • 加速!
    滾動完之後 Locality 跟著變好
    會變快許多
    可以搶 TopCoder
  • 練習思考
    做背包問題的滾動蠻吃思考的
    要好好想清楚要怎麼滾

習題呢?

  • 開頭 Reference 有一堆
  • 自己上 OJ 戳 Tag
  • 翻以前校培,資讀,etc.

Greedy & DP ?

為什麼 Greedy 不行就 DP ?

當 Greedy 不對就代表

「這個問題不能只由這個子問題轉移」

換句話說,Greedy 就是可以證明只有一個轉移來源的 DP

 

reference: baluteshih @2021TOI 1! 簡報

強烈建議各位去翻翻看

恭喜你學完競程所有東西了(誤

「寫程式不是二分搜就是 DP」

Made with Slides.com