0 問題描述
問題1(CF390C): 給定若干行聊天記錄 “發言人: 話”, 有些聊天記錄的”發言人”是缺失的, 已知相鄰對話發言人一定不同, 且每個發言人說的話裏都不會包含自己的名字, 現給定可能的發言人的集合, 請填充每個對話的發言人.(來自Codeforces Round 390: http://codeforces.com/contest/754/problem/C)
問題2(LC36): 給定一個9*9的矩陣, 有些元素爲空, 要求使用1-9填充空元素, 使每行, 每列, 每個3*3小矩陣(共9個)中都包含1-9這9個數字.(來自 leetcode: https://leetcode.com/problems/sudoku-solver/?tab=Description)
先說結論, 兩個問題非常相似, 但是第一個問題可用動態規劃求解; 由於狀態轉移方程極難表述, 第二個問題不能用動態規劃求解
1 用一張表表述問題
對於LC36, 在初始條件下, 如果我們不對矩陣進行任何填充, 那麼問題的當前狀態可以用一張表進行描述, 注意, 我們可以按照從上到下,從左到右的方式遍歷矩陣的每個元素, 這樣我們用一維序列描述二維矩陣:
元素編號 | 可能的填充數字 |
---|---|
1 | |
2 | |
… | … |
81 |
同樣地, 對於CF390C, 在我們填寫發言人之前, 問題的初始狀態也可以用一張表描述:
聊天記錄 | 可能的發言人 |
---|---|
第1條記錄 | |
第2條記錄 | |
… | … |
第n條記錄 |
一旦我們開始填寫矩陣/發言人, 那麼上述兩張表的狀態就會改變, 而且改變總是會讓每行的”候選集”縮小.即, 我們列出的每行的候選集一定是最終結果的超集.
2 使用動態規劃描述問題
在LC36的題意下,
在390C的題意下,
乍看上去, 這兩個問題是一樣的. 可是我們遺漏了一很關鍵的東西, 就是我們對
3 重新描述問題
對於LC36:
(i,j) 表示第i 個格子, 使用j 填- 問題跳轉到
(i+1) , 引入額外的約束:”不能使用j ”, 爲了表述額外約束, 我們需要修改問題的表示形式 - 於是我們返回去重新表述
(i) : 填寫第i 個格子, 使用數字j , 不能使用的數字列表爲k , 記爲(i,j,k) - 問題再次跳轉到
(i+1) : 填寫第i+1 個格子, 使用數字j′ , 不能使用的數字列表爲k,j , 記爲(i+1,j′,[k,j′])
這樣可以構造DP問題
而對於390C:
(i,j) 表示, 填寫第i 行, 使用人物j - 然後跳轉到
(i+1) , 問題表述爲填寫第i+1 行, 使用人物j′ , 不能使用人物j , 引入新的約束, 需重新表示問題 - 重新表述
i 爲, 填寫第i 行, 使用人物j , 不能使用人物k , 記爲(i,j,k) - 再次跳轉到
i+1 , 問題表述爲填寫i 個格子, 使用人物j′ , 不能使用人物j , 記爲(i+1,j′,j)
至此, 可以構造DP問題
4 分析
這兩個問題再以下方面是相同的:
- 都是一個一個的填寫, 並且每個都有個候選列表
- 填寫一個以後, 會影響其他人的填寫
但是CF390C和LC36相比有個關鍵的不同點: 前者跳轉時, 會不攜帶來自
- 對於CF390C, 問題的跳轉方式是
(i,j,k)⟶(i+1,j′,j) ,(i+1) 的第三個狀態與i 的第三個狀態無關. - 對於LC36, 問題的跳轉方式是
(i,j,[k])⟶(i+1,j′,[k,j]) ,(i+1) 的第三個狀態與i 的第三個狀態有關.
至此, 我們發現第一個是普通的動態規劃, 第二個似乎是狀態壓縮動態規劃.可是真是這樣嗎?
對於CF390C, 我們在計算
j′ : 遍歷每個可能的發言人j : 直接使用問題i 的第二個狀態填充
然而, 對於LC36, 我們在計算
- 元素在矩陣中受到的天然限制: 行, 列, 小3*3小矩陣中的元素.這個限制就是第1節中的表的限制.
- 元素受到
i 的限制: 父元素i 有自己的不可用數字集合, 這個集合中的數字來自於父元素i 的表, 以及父元素i 的父元素(i−1) .
這樣一來, 如果你要計算
- 對於受到原始限制的數字, 我們可以通過
i 和i+1 的想對位置, 進行修正. 比如, 若i 和(i+1) 在同一行,且在同一個小矩陣中 那麼我們從i 的原始限制集中去掉通過列限制提供的數字. - 對於受到
i 的先前元素限制的數字, 我們必須找到他們的生成位置, 然後根據生成位置和(i+1) 的想對位置, 來計算(i+1) 的不可用數字集合.
稍加思考就會知道, 上述兩個步驟, 每一步都是幾乎不可實現的.因此, LC36雖然和CF390C非常像, 但是後者可以用DP求解, 但是前者很難用DP求解.