1. 程式人生 > 其它 >狀壓dp列舉子集原理

狀壓dp列舉子集原理

for(int S1=S;S1!=0;S1=(S1-1)&S){
    S2=S^S1;
}

其中S1就是我們列舉得到的子集,S2是S1在S內的補集,即S1∪S2=S

贅述如下:

現在來講一講為什麼是這樣的一個列舉方法,先讓我們來舉一個例子來模擬一下。

假設我們當前要列舉的是(10110)2的子集(子集仍然用S1表示):

S1=(10110)2−>(10100)2−>(10010)2−>(10000)2−>(110)2−>(100)2−>(10)2

根據例子,我們發現按照上面程式碼得到的結果是正確的,並且是把子集按照從大到小的順序枚舉出來的。
那麼接下來我們來談談這樣列舉的正確性。

首先,一個集合它自己本身也是自己的一個集合,所以我們從這個集合本身開始列舉。

既然是列舉,那我們就先考慮把當前列舉得到的子集先−1,但是這樣做不能保證−1後得到的狀態是原狀態的子集,

但是我們注意到:根據與運算&的性質,我們不難發現如果兩個數a,b,a<b,我們對這兩個數進行&運算,最後的結果一定是b的子集,因為我們與運算&得到的結果,在二進位制中出現1的位,b中一定也是1。

現在已經說明了這樣做確實得到了原集合的子集,但是還沒有說明我們已經列舉完了原集合的子集。

其實列舉子集就相當於在原集合的二進位制狀態下把一些1換為0,

而我們每次−1然後進行與運算其實就是在把當前子集的最右邊的1的右邊全部變為1,自己變為0,

然後進行與運算把新增的1中不該出現的抹去,最後只剩下了原集合中存在的1了。