動態規劃的轉移(transition)
一旦你知道狀態是什麼,轉移就是那條告訴你「如何由較小狀態的答案算出某狀態答案」的規則。它是動態規劃的動詞:狀態是你儲存的名詞,轉移是把它們連起來的東西。它通常帶有這樣的味道:「要解這個狀態,考慮我能做的每一個第一步選擇,解出每個選擇留下的剩餘子問題,再在這些選擇間取最佳(或求和,或計數)。」把轉移寫對就是把遞迴關係式寫對,而它的正當性來自最佳子結構。
在機制上,轉移把 dp[state] 表達成 dp[較小狀態] 加上連接它們之選擇的即時成本或價值。以 0/1 背包為例,在物品 i、容量 w 處的轉移是:dp[i][w] = max( dp[i-1][w], value[i] + dp[i-1][w - weight[i]] ),其中第一支跳過物品 i,第二支拿取它(僅當 weight[i] <= w 時允許)。把它念出來:用前 i 件物品的最佳價值,是「不拿物品 i(於是前 i-1 件仍有全部 w 可用)」與「拿它(賺到它的價值卻花掉它的重量,剩 w - weight[i] 給其餘)」兩者中較好的那個。每一支本身都是一個最佳子答案——這就是最佳子結構在運作。
轉移也決定了你動態規劃的成本,因為計算一個狀態的工作量,就是它轉移中所考慮的選擇個數。0/1 背包的轉移在 O(nW) 個狀態上每個只做 O(1) 工作,所以總共 O(nW)。一個要遍歷 O(n) 個前驅的轉移(如最長遞增子序列的簡單遞迴)每狀態花 O(n),橫跨 n 個狀態,所以是 O(n^2)。誠實的警告:一個轉移只有在「給定狀態下它所引用的子問題確實彼此獨立」時才正確——若兩支可能暗中互相干擾(共用一項狀態未追蹤的資源),那這條遞迴即使看起來合理也是錯的。
編輯距離在 (i, j) 的轉移:若 A 的第 i 個字母等於 B 的第 j 個,dp[i][j] = dp[i-1][j-1](免費配對);否則 dp[i][j] = 1 + min( dp[i-1][j] 刪除, dp[i][j-1] 插入, dp[i-1][j-1] 替換 )。每一格試這三種編輯與無成本配對,取最便宜者。
轉移列舉第一步選擇並重用最佳子答案——把最佳子結構化為具體。
一個「看起來對」的轉移不是證明。要確認它涵蓋了每種情況(沒漏掉選擇)、只引用彼此獨立的子問題,且永不讀取尚未算出的狀態——這正是轉移無聲出錯的三條途徑。