關於狀壓DP列舉子集的方法與理解
阿新 • • 發佈:2022-05-23
我們現在要列舉狀壓集合 \(S\) 的子集,程式碼實現:
for (int S1=S;S1!=0;S1=(S1-1)&S) {
S2=S^S1;
}
其中 \(S_1\) 就是我們列舉得到的子集,\(S_2\) 是當前子集 \(S_1\) 在 \(S\) 內的補集,即 \(S_1 \bigoplus S_2 = S\)
\[{\because S_2 = S \bigoplus S_1} \] \[{\therefore S_2 \bigoplus S_1 = S \bigoplus S_1 \bigoplus S_1} \] \[{\therefore S_2 \bigoplus S_1 = S \bigoplus (S_1 \bigoplus S_1)} \] \[{\therefore S_2 \bigoplus S_1 = S \bigoplus 0} \] \[{\therefore S_2 \bigoplus S_1 = S} \]贅述如下
現在來講一講為什麼是這樣的一個列舉方法,先讓我們來舉一個例子來模擬一下。
假設我們當前要列舉的是 \((10110)_2\) 的子集(子集仍然用 \(S_1\) 表示):
\(S_1 = (10110)_2 \Rightarrow (10100)_2 \Rightarrow (10010)_2 \Rightarrow (10000)_2 \Rightarrow (110)_2 \Rightarrow (100)_2 \Rightarrow (10)_2\)
根據例子,我們發現按照上面程式碼得到的結果是正確的,並且是把子集按照從大到小的順序枚舉出來的。那麼接下來我們來談談這樣列舉的正確性。
首先,一個集合它自己本身也是自己的一個集合,所以我們從這個集合本身開始列舉。
既然是列舉,那我們就先考慮把當前列舉得到的子集先 \(-1\)(即 \(S_1 - 1\)),但是這樣做不能保證 \(-1\) 後得到的狀態是原狀態的子集,但是我們注意到:根據與運算&的性質,我們不難發現如果兩個數 a, b,a < b,我們對這兩個數進行&運算,最後的結果一定是b的子集,因為我們與運算&得到的結果,其二進位制中所有1出現的位置在b的二進位制對應位置也是1。
現在已經說明了這樣做確實得到了原集合的一個子集 \(S_1\),但是還沒有說明我們通過上述方式列舉,可以列舉完原集合 \(S\) 的所有子集。
其實列舉子集就相當於在原集合的二進位制狀態下把一些1換為0,而我們每次 \(-1\)
Preference
- 關於狀壓DP列舉子集的方法與理解 備份此貼
- 二進位制狀態壓縮列舉子集 從另一個角度證明
- OI Wiki - 位運算