1
動態規劃 Dynamic Programming
之前曾說過分而治之的概念便是藉由解決子問題來解決母問題。動態規劃跟分而治之有幾分 相似,但未必是藉子問題而解決母問題,而是藉相關問題的答案推導出欲求問題之答案。不過為 了方便,通常還是以子問題、母問題稱之。
特色
動態規劃有以下幾個特點:
1. 重複子問題
所謂重複子問題是指,常常一個子問題會被多個母問題所用到。所以動態規劃會藉由記下子 問題的答案來加速。
2. 狀態與轉移
狀態與轉移是動態規劃中重要的一環。狀態其實就是一組數字,能夠唯一的描述我們所要求 的子問題,狀態與問題可以視為一個一對一的函數。而轉移其實就是一條式子,這個式子描述了 我們如何從一個或多個狀態的值推導出一個新的狀態的值。
3. 最佳子問題
一個問題若要用動態規劃解決,則他必須有最佳子問題的性質。也就是說,對一個最佳解而 言,他的任意一個局部都是該子狀態的最佳解。
4. 無後效性
若轉移到子狀態時的決策會影響到子狀態轉移到母狀態時的決策選項,我們說這個這組狀態 有後效性。我們無法在一個有後效性的狀態下定義最佳解,自然無法符合最佳子問題。所以當狀 態有後效性時,我們就可能得以別種狀態來描述問題。
實現
動態規劃通常以下面兩種方法來實作:
1. Bottom - Up
Bottom - Up顧名思義就是由下而上的DP。在計算一個狀態之前,先將所有他會用到的狀態
算出。如此一來,在計算新的狀態時只需直接取值即可。這種作法的好處是通常可以直接用迴圈 來跑,所以不會有太大的常數。缺點是會計算到一些可能不會用到的狀態。
2. Top - Down
Top - Down與Bottom - Up是個完全相反的概念。他是在計算一個狀態時,如果發現有某個
需拿來轉移的狀態尚未被計算,就遞迴下去先行算出該子狀態的答案並記起來。相對Bottom - Up 這種作法有時比較好實作,而且沒用到的狀態也不會被計算到。然而呼叫遞迴會消耗時間,所以 在狀態幾乎都用到的狀況下,此種方法會比Bottom - Up還慢。另外,使用這種方法有時需注意 遞迴過深的問題。
DP時到底要使用哪種方法見仁見智。只要寫得順就是好方法。
經典基礎應用
1. 總和序列
《最大子矩陣 (93北市賽problem 3)》TIOJ 1122
給你一個n x m的矩陣,每格上皆有一值。求總和最大之子矩陣其總和有多大?
2
2. 組合計數
3. 背包問題
例題
《抵抗異形軍 (第一屆快樂暑假營第三次練習賽)》TIOJ 1384 有n艘異形軍將來襲,每艘都有其來襲的時間跟高度。
現在限制你這次擊落的異形軍高度必須大於等於上次擊落的,請問你最多能擊落幾艘?
《芳佳的打工 (第一屆快樂暑假營第三次練習賽)》TIOJ 1385 你有三種操作:增加一個字、刪除一個字、修改一個字。
現在給你兩個字串A、B,請問要將A修正成B至少需要幾次操作?
《國王烏龜的接駁車 (第三屆快樂暑假營第二次練習賽)》TIOJ 1623
有n隻烏龜,搭往皇宮的專車有k 班,每台車重量負荷都一樣是m 公斤。依序給你每隻烏龜
的重量wi,每隻烏龜依序走來時,只能叫他立刻上車或是把他踢開。當那隻烏龜上去會超過
該台車的負重時那班車就會開走,下一班會立刻過來。求最多能讓幾隻烏龜上車?
《A遊戲 (96建中校內資訊能力競賽problem 6)》TIOJ 1029
給你一個序列,現在有兩個人輪流把序列最左端或最右端的值拿走。假設兩個人都是絕頂聰明 的人,且都希望自己拿到的總和盡可能比對方多。求先手與後手最後總和各是多少?
《Ragnarök (第一屆快樂暑假營複習賽)》TIOJ 1411
給你一個用-1, 0, 1組成的序列。請求出最長的一段使該段的總和為0。
《小豬Piggy (TOI2004初選problem 2)》TIOJ 1196
給你一張n x m的地圖,每格會有一個值或是X,X者不可通行。
現在你每次只能往右或往下走,求從最左上走到最右下時路徑上格子總和值最大是多少?
《兔子跳鈴噹》TIOJ 1019
給你 n個鈴鐺的水平位置。你每次可以從第x個鈴鐺跳到第x+1或第x+2個鈴鐺。
求從第一個鈴鐺跳到第 n個鈴鐺所需的最小移動水平距離總和為何?
《Striker的祕密 (第一屆快樂暑假營第三次練習賽)》TIOJ 1387 有n種魔石,每種魔石的各有其重量w、魔力值m、數量c。 現在你最多可以攜帶T的重量。求魔力值總和最大能是多少?
《N箱M球 (雄中公假社'08 入退社考)》TIOJ 1291
求N個相同的箱子放入M顆不同的球的方法數%1000000是多少?
《名偵探蚵男 (NPSC2003初賽problem F)》TIOJ 1043 給你n種數字,求有幾種湊出m的組合?
Greedy
每一選擇都採取在當前狀態下最佳的選擇。
Solution 1. 估計
估計(證明)Greedy 的正確性。
2. 構造
透過其他資料結構及算法實現 Greedy。
Classic Problems 例題 1 - 零錢問題
給你無限量個一元、五元、十元、二十元、五十元、一百元的硬幣,現在 你要湊出 n 元來,請問最少需要多少個硬幣?
例題 2 - 工作排程 1
給你 n 個事件,每一個事件都有它的開始時間和結束時間,而在一個時間 內最多只能夠有一個事件,請問最多總共可以進行幾個事件?
例題 3 - 工作排程 2
給你 n 個工作,每個工作都有其截止期限 ti,而如果你在時限前沒做那個 工作的話,則要付罰金 ci。如果做一個工作需要 1 單位的時間,則你最少要付多 少罰金?
例題 4 - Minimal Coverage (ACM 10020)
給你 n 段線段[ai, bi],請問你至少需要幾段線段,才能覆蓋住[0, M]這段線 段?
例題 5 - 刪位數的問題 (TIOJ 1397)
給你一個 n 位數的正整數 A,請問刪除其中 k 位數 ( k < n )、將剩下的數字 依序合併形成一個新的正整數 B,B 的最小可能值是多少?請注意,A 和 B 的首 位都不能是 0。
例題 6 - 誰先晚餐 (NPSC 2005) (TIOJ 1072)
給你 n 個要吃晚餐的人和一個廚師。當然那些人可以同時一起吃,但廚師一 次只能做一道菜。給定每個人要吃的菜需要做多久,以及那個人需要吃多久,試 問至少要多少時間,才能讓全部人都吃完?
例題 7 - 晶片設計 (TIOJ 1316)
一排晶片兩兩希望連成一對(不一定相鄰),但由於空間限制,晶片間的連線 最多只能上方一條、下方一條,即同一鉛直位置上至多兩條線,問最多能連結幾 對晶片?
例題 8 - 功夫城堡 (TIOJ 1401 )
在一個 N*N 的棋盤上放置 N 個給定範圍的城堡,是否能使得他們不能互 相攻擊?
例題 9 - 寵物雞問題 (TIOJ 1231)
寵物雞每一秒可以吃ㄧ個食物,並得到那個食物的熱量 i,然後增胖 i kg。
如果沒有吃東西會減重 1kg。但是食物有保存期限 k,所以在 t 秒時最重可以把 寵物雞養肥多少公斤?
例題 10 - 骨牌遊戲(NPSC2004)(TIOJ 1432,1465)(+二分搜)
把有n 個數字的序列分成w 段,使每段的最大值最小。(全部數字<1001)
例題11 - LIS but not LIS ( TIOJ 1240 )
給你一個有n個正整數的序列,請你把它分拆成最少條的序列,使得每個序 列都是嚴格遞增的。
例題12 - 倍因道 ( TIOJ 1241 )
給你1~n,你可以你可以選擇把每個數分到兩種集合的其中一種:"倍數"
和 "因數" 。請問這兩個集合間最多有幾組(因數-倍數)關係?
例題13 - 萬里長城(TIOJ1441)
給定一條數列,請求出一條子序列滿足:
1. 一個大一個小的順序。
2. 兩端的數都要比相鄰的數還大。
例題14 - 分數背包
有n 樣物品,均有其價值與重量,有一個最大載重量w 的背包去裝這些物 品,物品可以切割(切割後仍有相應成比例的價值),最多可以裝多少價值的 東西?
例題14 - 炒菜問題 (TIOJ 1221)
Advanced Tasks
Toy Cars (POI XII) O(nlgn)
強尼很可憐,房間很小(只能裝k 個玩具),櫃子很高,玩具都要請媽媽幫他 拿。他媽媽很強,知道他玩玩具的順序,請問媽媽最少要幫她拿下幾次玩具?
Weights (POI XIV) O(nlgn)
給你很多箱子(有重量限制)跟很多物品(有重量),請問最多可以裝幾件物品 呢?條件:對於所有重量,兩兩都是因數/倍數關係。
The Ninja Way (ACM ICPC 2009 Southeast USA Regional) O(n2)
有一排樹,Ninja 依照一定順序跳,可是 Ninja弱到他最多只能跳k 的距離。
題目告訴你 Ninja跳的順序,請問他從起始點到終點最多可以跳多遠?
Hash 雜湊表
當我們儲存一個無法用正常的索引來表示的資料時,我們會給他一個編號 h,而那編號 對於這筆資料是唯一的。我們可以用動態記憶體把資料存在相鄰串列 data[h]裡。
就這樣說好了,假如我們有個資料庫要處理字串,如果每次作查詢需要 O(n) 就太慢了,
那我們把字串分類好了,以字首前三個字來分類。可是像 con-, dis-字首的資料太多了,分 類後在 con- 索引下查詢也要 O(k) (k 是 con-索引下資料的筆數)。那我們就用一個不是正常 人會做的事:亂弄一個 hash 函式,幫資料作奇怪的分類。比如說 :
hash(str) = ( str[0]*81 + str[1]*27 + str[2]*9 + str[3]*3 + str[4]*1 ) mod k 所以可以最多只可能製造出k種 hash 的編號。
儲存:
在 data[hash(str)]裡用動態記憶體增加一筆 str 的資料。
通常我會查詢 str 是否已經存在 data 裡面,再看要不要增加 str 的資料。
查詢:
查詢 data[hash(str)]裡所有的資料。
※ k 通常會用質數,然後只要電腦記憶體容許的話,越大越好。
這樣的話每次查詢平均複雜度就只要 O(n/k) (n/k 通常很小,所以就當他是 O(1))了。
為什麼說平均呢?因為可能很不幸的你 hash 函式太失敗了,資料分的不夠平均,可能 集中在其中一個 data[i] ( 0≤i≤k )。
這種情況很少發生,除非你 RP 低到不行。
※hash 的函式請發揮想像力 XDD。
循序 hash 平衡樹
查詢 O(n) O(1) O(lgn)
增加 O(1) O(1) O(lgn)
踢掉 O(n) O(1) O(lgn)
查詢比他大(小)的下一筆資料 O(n) X O(lgn)
coding 複雜度 簡單 中 繁複(除非用 map, set)
例題 1 – 撿鞋運動 (TIOJ 1302) 給你許多詢問,如下:
1. add 加入一筆資料,分別是兩個字串,分別代表死者名字跟死法。
2. chk 查詢,可能查詢死者名字也可能查詢死法。
3. del 刪除一筆資料。