枚舉(Enumerate)
枚舉是最直觀的演算法,列出所有可能的解,並判斷是否符合題目要求。容易寫出,但通常時間複雜度太大無法滿足題目時限。
通常在設計枚舉找出需要枚舉的參數,並選擇是要用迴圈或遞迴方式。如果評估時間複雜度的時候發現太大時,可搭配一些技巧來降低時間複雜度。
特殊枚舉型態
集合
如果題目要求和集合有關係,可利用二進位表示一個集合,第 位代表第 樣物品選或不選 (0 或 1)。時間複雜度 ,若執行時限為 秒,枚舉大小最多約 。
順序
如果題目要求和順序有關係,可利用 <algorithm>
內的 next_permutation
或 prev_permutation
達到枚舉元素的先後順序。時間複雜度為 ,若執行時限為 秒,枚舉大小最多約 。
技巧
減少枚舉維度
找出需要枚舉的參數後,有些參數可能是和結果無關,或是可由其他參數推導出,移除該參數可降低時間複雜度。
uva10976 - Fractions Again?
給定 ,請求出所有正整數解 ,使得
最直觀的解法是枚舉兩個參數 ,但其實只要知道 任意一項就可推出另外一項,有根據題目我們可以得出 在 之間(當 時, ),要枚舉的範圍較小,因此我們選擇枚舉 的算法,時間複雜度為
參考程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
雙指標
利用兩個指標線性遍歷陣列,得出答案。「雙指標」可為兩個指標或是兩個整數型態變數紀錄位置。
Question
給定一個長度為 的序列 ,求出一組 使得 最大
首先會直觀的想到一個 的算法:枚舉 算出結果後取最大值。 接著可以想到對於每個數字 只要和最小的 ( ) 相減即可,又 具有單調性,即 的位置會不變或向右移,因此可以利用一個指標遍歷序列,一個指標紀錄當前 的位置,時間複雜度 。
參考程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
11572 - Unique Snowflakes
給定一個長度為 的序列 ,求出最長序列,當中沒有重複的數字
我們可以設兩個指標,左指標和右指標,每次迭代右指標先往前一個位置,如果左右指標之間有重複的數字,就將左指標往前一個位置,直到沒有左右指標之間沒有重複的數字。利用 set
來維護是否有重複數字,時間複雜度 。
參考程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
折半枚舉
有時遇到複雜度 的算法,在無法用其他方法降低複雜度情況下,可以試著將元素切成兩半,分別算出答案,再用其他算法組合起來,時間複雜度通常為 或 等。
uva01326 - Jurassic Remains
給定 串英文字串,請最多可以包含幾個字串,使得這些字串內每個字元都出現偶數次。
先把每個字串轉成一個二進位,第 位表示字串有(0: 偶數,1: 奇數)個字元 i (0:A, 1:B, ...)。如果有一堆字串內每個字元都出現偶數次,那麼他們的 xor 值 。
這題關於「集合」,可用二進位枚舉,加上判斷,時間複雜度 。
這題更快的做法是拆半枚舉,把前 和後 字串分別枚舉,分別把結果存在不同的 map 裡面,如果兩個 map 有相同的 xor 值,代表兩個集合的字串合起來,每個字元都出現偶數次。這種做法時間複雜度為
剪枝
在使用遞迴枚舉時,當搜尋到一組解答,發現該組解和其延伸的解,皆無法達到達到需求,就停止搜尋,改搜尋其他組解,該技巧叫做「剪枝」。
明確地來說,以下狀況需使用「剪枝」:
- 發現解答是不合法的。
- 在最佳化問題,發現無法成為最佳解。
Qusetion
給定一個數字 ,要你求出有幾組正整數解 ,使得 。順序不同視為相同解,例如 和 視為相同組解。
為了避免算到重複組合,我們讓 。
設目前解總和為 ,最後一項為 ,下一項 就從 開始嘗試,當嘗試到 時,就停止嘗試(剪枝)。
參考程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|