Chapter III. 漸入佳境
貪心演算法
貪心法 II
作者baluteshih
先備知識貪心法 I

自己找出「選擇」


針對貪心法 I 提到三個例題裡,我們總是被題目要求要執行一連串的「任務」:

這讓我們能為所有的決策排出一個順序,使得我們能每次挑選最好的那項執行。

但有的時候這個所謂的「任務」可能不會寫在題目裡面,我們不妨來看看下面這道例題:

例題
Product of digits

給你一個大於等於 $0$ 的整數 $N$,請你你找到最小的非負整數 $Q$ ,使得在 $Q$ 中所有位數(digit)的乘積等於 $N$。若找不到任何非負整數的位數乘積為 $N$,請輸出 $-1$。

例如:$N=10$,可以找到 $Q=25$,因為 $2\times 5=10$。

條件限制
  • $T \leq 100$ 筆測資
  • $0\leq N\leq 10^9$

不曉得讀者對於上面這題是否有感受到問題了,那就是我們可能不知道從何開始「選擇」,畢竟我們沒有被指派所謂的「任務」。

遇到這種題目,我們必須要先一些簡單的觀察,首先搞清楚我們目標為何——找到最小、滿足條件的非負整數。

數字是如何比大小的呢?要注意到這裡不是簡單的用一個 < 運算子就好,因為題目關注的是數字的「位數」,所以我們可以想成當我們有兩個數字字串時,該如何去比較他們的大小:

因此,要找到最小的非負整數,我們的目標會是:

而這樣的觀察會引導出我們真正的任務為何:試圖不斷選擇最好的位數來乘出 $N$。此時便有個很單純的結論:不斷找到 $N$ 的因數中,$2\sim 9$ 內最大的那個,並將其放到數字的目前最高位。

會採取這樣的策略主要有以下幾個優勢:

如此一來我們就完成這題了。不過我們還是總結一下剛剛發生了什麼:

透過這道題目,希望能讓讀者感受到選擇並不總是題目給的,同時也期盼能給讀者一點「觀察性質」的啟發。

「證明」你的策略


在貪心法的章節中,到目前為止我們似乎都只是很感性的去理解我們策略的正確性而已,不過如果比賽中我們「猜錯策略」導致浪費了時間撰寫這些錯誤解答的話,往往會造成不小的損失。

因此,在要寫解答之前,我們可以簡單的幫自己的演算法給出一點「證明」,來增加我們對策略的自信心、或是提早預防自己寫出錯誤的解答。我們不妨就拿先前解過的幾道例題來解釋。

目標本質法

讓我們來證明這題的策略正確性:

例題
[Tutorial] 好吃的蛋糕
Source:NCOJ 97

有 $N$ 塊蛋糕,經過你審慎評估後,你了解到對於第 $i$ 塊蛋糕他的好吃度為 $y_i$。但無奈的是由於你食量有限,你只能吃下 $K$ 塊蛋糕。

試問在最佳策略下,若要最大化吃下的蛋糕好吃度總和,這個總和最大可以是多少?

條件限制
  • $1 \leq K \leq N \leq 2\times 10^ 5$
  • $1 \leq y_i \leq 10^ 9$

僅這題的策略看似很直接,但如果要稍微證明的話,還是可以講個一句話:因為我們獲得的好吃度是 $K$ 個數字的總和,既然我們都選了最大的 $K$ 個數字,那就不可能更好了。

像這樣直接找到「我們要最佳化的目標,有個顯然的上界」,往往能迅速的讓人接受策略的正確性。因為只要我們的策略能夠達到這個所謂的上界,那就沒有任何理由反駁他了。

反證法

讓我們來證明這題的策略正確性:

例題
[Tutorial] 趕不完的作業
Source:NCOJ 98

有 $N$ 份作業,你知道每一份作業你都只需要花一小時就能寫完,但可怕的是,第 $i$ 份作業再過 $d_i$ 小時就要截止了!

想要讓所有作業在截止前完成可能還有些困難,因此,你希望能完成越多份作業越好。試問在最佳策略下,你能有幾份作業在截止時間前寫完?

*註:若一份作業一小時後截止,你有辦法馬上開始寫這份作業來趕上死線

條件限制
  • $1 \leq N \leq 2\times 10^ 5$
  • $1 \leq d_i \leq 10^ 9$

這題的策略是「每次選擇死線最早的作業做」,要如何證明這個策略的正確性呢?

不妨我們就假設「最佳策略不能先選擇死線最早的作業做」,這樣會如何呢?

對於這個「真正的最佳策略」,假設死線最早的作業是作業 $m$,若

既然我們改成先選擇死線最早的作業做都不會讓答案變差,這就造成了矛盾,因此,我們的假設是錯誤的,也就得到了我們策略的正確性。

像這樣透過「矛盾假設」的手法就稱為「反證法」,也是在證明貪心演算法時常見的一個手法。

逆序數對證明法

讓我們來證明這題的策略正確性:

例題
誰先晚餐

有 $N$ 位客人來吃飯,其中第一個人的餐點需要煮 $C_i$ 分鐘,且他需要花 $E_i$ 分鐘才能吃完他的餐點。你身為餐廳老闆,你知道你的廚房同一時間只能煮一道料理,但每位客人都可以互相不干涉的享用自己的餐點,而一道料理煮完後便能馬上讓對應的客人開始吃,也就是說,當你在第 $t$ 分鐘開始煮第 $i$ 位客人的餐點,那麼這道餐點會在第 $t+C_i$ 分鐘後煮好,且這位客人一定會在第 $t+C_i+E_i$ 分鐘後享用完畢。

試求在最佳的烹飪順序下,最後一個人享用完他的餐點時間最早可以多早?

條件限制
  • $N\leq 10000$
  • $1\leq C_i, E_i \leq 1000$

這題的策略是「選擇吃最久的人點的餐點開始製作」,因此直接對用餐時間大到小排序就可以得到最佳順序,要如何證明這個策略的正確性呢?

這裡我們要使用的證明手法同樣是由反證法出發,但因為手法本身還多用到了一個巧思,因此才特別拿出來講。

首先,我們同樣假設「最佳順序不能經由用餐時間大到小排序」得出,因此,最佳順序肯定存在「相鄰的逆序數對」。

這時候,如果我們把 $i$ 和 $j$ 交換,會得到什麼呢?

我們不妨比較兩者造成的「用餐結束時間最大值」:

因次,透過上面的比較,我們就能發現交換相鄰逆序數對也不會讓答案變差,造成矛盾。

這類證明手法儘管比較不直覺,但卻能提供非常直接的正確性證據。

反過來利用證明方法解題


目標本質法

讓我們試著解決這題:

例題
Yet Another Bookshelf

有一個長度為 $n$ 的書架,我們以序列 $a_1, a_2, \ldots, a_n$ 表示這個書架的狀態,$a_i=1$ 代表書架上有書,$a_i=0$ 則代表沒有。

現在你可以執行以下操作若干次:

  • 選擇一個區間,並將區間內的書全部往左或往右移動一格,條件是書不可以重疊、也不可以超出書架的範圍。

請問你至少要執行幾次操作才能讓所有的書全部相鄰,也就是形成一個連續區間?

條件限制
  • $t\leq 200$ 筆測資。
  • $1 \leq n \leq 50$
  • $0\leq a_i\leq 1$

這題有一個很直接的貪心做法:每次選擇最左邊的連續一段書,將其全部往右一格。

但這樣直接移動可能會花費 $O(n^2)$ 的時間,雖然足以通過這題、要優化其實也不難,但有沒有更單純的做法呢?

可以觀察到,我們這樣的策略存在著一個直接的本質:每次移動,一定是在讓最左邊和最右邊兩本書中間的 $0$ 少一個。

反過來想,任何移動不也只能讓這個「中間的 $0$」的個數少一嗎?既然我們的策略能貼合這個「答案下限」,那我們的策略就是最佳的了。

因此,我們直接的證明了我們策略的最佳性──與此同時,我們還順便找到了一個超級好寫的作法:直接找到最左邊和最右邊的書,數中間有幾個 $0$ 就好!

這樣的做法不僅是 $O(n)$,還非常好寫。從這題我們也可以了解到,貪心策略有時候不一定要真的去實作,他可以僅僅是作為真的存在理想答案的「證據」,來讓我們直接計算答案後輸出。

逆序數對法

讓我們來實際解決上次失敗的這題:

例題
物品堆疊

有 $N$ 個物品需要被堆在一個垂直的貨架上,已知第 $i$ 個物品的重量是 $w_i$,需要取用 $f_i$ 次,每次取用一個物品就需要將該物品上方的物品貨架升高才能做取用,因此取用一個物品消耗的能量為其上方的物品重量總和,而取用 $f_i$ 次就要花上 $f_i$ 倍的能量。

請問在最佳的物品擺放順序下,最小的能量消耗總和為何?

條件限制
  • $N\leq 10^5$
  • $1\leq w_i, f_i\leq 1000$

既然我們要幫所有物品排一個順序,不如大膽猜測:如果我們想要用逆序數對證明法證明我們的順序,會發生什麼事呢?

對於相鄰的兩個物品 $(i, j)$,交換他們與否同樣不會影響外側物品造成的花費,所以我們不妨專心比較交換這兩個物品的所造成的花費:

若要比較兩者的大小關係,可以發現:

既然我們確定了,每個物品都有唯一的數值可以拿來做比較,那事情就簡單了:直接對這個數字排序,照著模擬就行啦!所以我們便得到了一個 $O(N\log N)$ 的作法。

雖然我們在對分數做比較,但實際比大小的時候還是建議大家反著移項回去、使用交叉相乘的方式比大小,來避免浮點數可能產生的問題。

這類題目往往直覺上不好解決,但若透過倒推的方式,從證明手法開始思考的話,往往有時候可以神奇的把題目解決掉。

不過如果比賽要求快,真的需要這麼麻煩的去做假設、消項和移項嗎?這裡有幾個小技巧:

習題


習題
換錢錢
Source:NCOJ 715

在卡加布列島上發行的硬幣有 $N$ 種,分別價值 $A_1, A_2, \cdots, A_N$ 塊,已知 $1 = A_1 < A_2 < \cdots < A_N$,而且對於所有可能的 $i$,$A_i$ 是 $A_{i+1}$ 的因數。學姐帶了價值 $K$ 塊錢的小說去卡加布列島想要換成現金,但她希望拿到的硬幣越少越好,作為收銀員的你能滿足學姐的需求嗎?

(注意到最小硬幣是 $1$ 塊錢,所以一定有辦法換錢)

條件限制
  • $1 \le N \le 10$
  • $1 \le K \le 10^ {12}$
  • $1 \le A_i \le 10^ {12}$, $\forall$ $i$
  • $A_1 < A_2 < \cdots < A_N$
  • $A_i | A_{i+1}$, $\forall$ $i < N$
習題
換錢錢,但我窮
Source:NCOJ 716

卡加布列島上發行的硬幣有 $N$ 種,分別價值 $A_1, A_2, \cdots, A_N$ 塊,已知 $1 = A_1 < A_2 < \cdots < A_N$,而且對於所有可能的 $i$,$A_i$ 是 $A_{i+1}$ 的因數。

學姐這次帶了價值 $K$ 塊錢的遊戲王卡去卡加布列島想要換成現金,她一樣希望拿到的硬幣越少越好。作為收銀員的你,這次每個硬幣的數量都只有 $C_i$ 個可以給學姐換錢,不然你會被老闆罵。這次你能滿足學姐的需求嗎?

因為這裡的最小硬幣是 $1$ 塊錢而且你可以給學姐的 $1$ 塊錢有 $K$ 個,所以一定有辦法換錢,不用考慮無解的狀況。

條件限制
  • $1 \le N \le 10$
  • $1 \le K \le 10^ {12}$
  • $1 \le A_i \le 10^ {12}$, $\forall$ $i$
  • $1 = A_1 < A_2 < \cdots < A_N$
  • $A_i | A_{i+1}$, $\forall$ $i < N$
  • $C_1 = K$
  • $1 \le C_i \le 10^ 9$, $\forall i > 1$
  • 保證一定有解,不會發生湊不出來的狀況。
習題
Reserve or Reverse

給定一個長度為 $N$ 的字串 $s$,你可以執行下方一系列操作一次:

  • 選擇一個偶數長度的序列滿足 $1\leq x_1< x_2 < \cdots < x_{2k}$,序列長度可以為 $0$。
  • 交換 $s_{x_1}$ 和 $s_{x_{2k}}$。
  • 交換 $s_{x_2}$ 和 $s_{x_{2k-1}}$。
  • 交換 $s_{x_3}$ 和 $s_{x_{2k-2}}$。
  • $\vdots$
  • 交換 $s_{x_k}$ 和 $s_{x_{k+1}}$。

請問在執行一次操作後,得到的字串的最小字典序為何?

條件限制
  • $1\leq N \leq 2\times 10^5$
  • 字串 $s$ 只包含小寫英文字母。
習題
Movie Festival
Source:CSES 1629

有 $n$ 場電影,第 $i$ 場電影的開始時間為 $a_i$、結束時間為 $b_i$。在不考慮移動時間的情況下,若你同一時間只能看一場電影,你最多可以看幾場電影?

條件限制
  • $1\leq n \leq 2 \times 10^5$
  • $1 \leq a_i < b_i \leq 10^9$
習題
原料廠商
Source:NCOJ 721

小鴨工廠專門製造黃色小鴨,他們必須和塑膠原料廠訂購塑膠。一共有 $N$ 家塑膠原料廠,其中第 $i$ 家能從第 $s_i$ 天供貨到第 $t_i$ 天。

小鴨工廠想要選一些原料廠當合作對象。假設最早能供貨的原料廠開始時間是第 $S$ 天,能供貨到最晚的原料廠結束於第 $T$ 天,小鴨工廠希望從 $S$ 到 $T$ 的每天都要與一家原料廠合作。在此限制下,小鴨工廠希望合作廠商愈少愈好,請你回答至少需要幾家廠商。

條件限制
  • $1\le N\le 1000$
  • $1\le s_i\le t_i\le 10^ 5$
習題
円円攻略黃河
Source:NEOJ 74

住在月亮上山上的円円族,在舉行完祭月儀式後,並沒有順利解決問題,因為円円族長老所找到願意參加祭月儀式的円円數量實在是太少了。在那之後,由於月相的變異,導致了円円族主食高棕櫚的生產量越來越少,因此円円族們舉辦了部落會議來討論如何解決這個問題。

為了解決糧食問題,円円族決定要擴展他們的領土。円円族們原本居住在黃河流域(黃河又稱黃 River)北方的月亮山上,因為黃河曾經發展出黃河流域文化,円円族們看上了這一點打算要攻下黃河,佔地為王。經過部落會議之後,円円族們決定推派變態円円們去攻打黃 River。

居住在黃河流域的居民們得知這件事後,決定建造萬里長城來抵禦円円族的攻擊,請身為建築工程師的你幫忙。萬里長城的特色就是城牆一側總是一高一低的,居民們希望你造的城牆越長越好,而建造城牆的建材是石頭,每塊石頭可能有不同的高度。

但是供應石頭的廠商非常討厭,他每送一塊石頭就會問你要不要買,如果你不買,那塊就永遠不會再賣給你。而且時間非常緊迫,你一買一塊石頭就要馬上用,而且一定要按照購買的順序建造。

你透過一些內部的管道,得知廠商即將供應給你的石頭高度的順序,你能建造出多長的城牆呢?

有幾點可能要注意一下:

  • 石頭不能疊起來,也不能躺下來,不然間隔的接縫會不整齊。
  • 一定要滿足一塊高一塊低的方式,也就是建造好萬里長城的高度序列若為 $H$ 的話,$H_i$ 滿足 $H_i > H_{i - 1}, H_{i + 1}$ 或 $H_i<H_{i - 1}, H_{i + 1}$。
  • 城牆的兩側,一定要是比旁邊那塊還高,不然城牆會很醜。若萬里長城長度為 $L$,則滿足 $H_1>H_2, H_{L-1}<H_L$。
  • 這家廠商相當的優質,每塊石頭的長寬都是一單位,只有高度不均。
條件限制
  • $1\leq N\leq 10^6$
  • 石頭高度介在 $[1, 2^{31})$ 之間。
習題
填方格遊戲-續
Source:TIOJ 1279

填方格遊戲除了多人模式之外,還有一種不為人所知的單人模式。

有一個 $m$ 乘 $n$ 的方格,每格內有一個非負整數。一開始你的分數是零。每次你都可以選一個還沒被圈過的數,把他圈起來,並且計算出你圈的數字以及與他相鄰(有公共邊)的格子中有圈的那些的總和。算出這個總和之後,將這個總和加到你的分數。繼續這個操作直到整個方格都圈完了。

作為一個單人模式遊戲,你的目標當然是使你自己的分數愈高愈好。然而這個方格有點大,所以你決定藉助程式之力幫你玩這個遊戲。

條件限制
  • $1\leq m, n\leq 1000$
  • 方格內的數字皆為小於 $10^9$ 的非負整數。
習題
七彩繽紛銀河麵
Source:NCOJ 733

面試麵屋牡丹未果的赤井心,憤而在麵屋牡丹本店對面開了一家烏龍麵,想奪取麵屋牡丹的客源。為了具備和麵屋牡丹對抗的實力,赤井心開始認真研究烏龍麵的配方。她得知了一碗烏龍麵由以下兩個要素組成:麵團配方和調味料配方。
於是赤井心備齊了 $N$ 種麵團和 $N$ 種調味料,準備大展身手,搭配出 $N$ 碗烏龍麵。她發現每種麵團都與特定一種調味料會產生「絕配」。當第 $i$ 種麵團和某種調味料是絕配,會產生 $c_i$ 的美味程度;但也可能搭配特定一種調味料後會製造出地獄組合,損失 $d_i$ 點的美味程度。
如果某組麵團和調味料的搭配不是絕配,也不是地獄組合則美味程度為 $0$。浪費食材是不可饒恕的事,因此一種麵團一定跟洽一種調味料搭配,一種調味料也跟洽一種麵團搭配。
沒有學過演算法的赤井心來找你幫忙,她聽說你是個演算法高手。你能想辦法讓總美味程度最大嗎?

條件限制
  • $1\leq N\leq 5\times 10^ 5$
  • $1\leq a_i\leq N$
  • $0\leq b_i\leq N$
  • $0\leq c_i,d_i\leq 10^ 9$
  • $a_i\neq b_i$
習題
這是圖靈機嗎?
Source:NCOJ 727

一臺圖靈機是擁有一個讀針和無限長的一張充滿格子的紙當作記憶體的機器。今天,你發現圖靈機掉落了 $N$ 張紙片,其中第 $i$ 張上面恰好寫滿了一個由小寫英文字母所組成的字串 $s_i$!你知道,圖靈機在壞掉之前想要做的事是:

  • 請將所有的 $s_i$ 排列,使得排列之後將這些字串頭接著尾黏起來的字典序最小。

舉例來說,假設你有三個字串 $[ab, abc, b]$,那如果排成 $[ab, b, abc]$ 的話,就會比 $[abc, b, ab]$ 還要小,因為前者拼起來是 abbabc 而後者拼起來是 abcbab

條件限制
  • $1 \leq N, \sum |s_i| \leq 10^ 5$