[算法複習]狀壓DP 更新中

emmm突然想來講講狀壓DP,這次先簡單講講狀壓DP是什麼和枚舉子集的方法吧。
有時間的話會做點題整理一下放上來。

狀壓DP

狀態壓縮

大概是這樣,比如你有n個物品,可以取也可以不取,那麼我們用1代表取了,0代表枚舉,排成一列長度爲n的數列,就可以代表n個物品的取捨狀況。
然後我們將這個數列視爲一個二進制串,就得到了一個狀態,比如11101。這就是狀態壓縮。
一般來說對於一個狀態\(f[11101_{(2)}]\)\(f[29_{(10)}]\),我們可以從它的子集轉移到它。
所以我們下面來討論如何枚舉子集

子集枚舉

來看一段僞代碼

for(s1 = s; s1 >= 1; s1 = (s1 - 1) & s)

首先我們可以知道枚舉出來的s1必然是s的子集,因爲s是s的子集,一個數&s的結果肯定也是s的子集
接下來我們考慮爲什麼s1可以枚舉完所有s的子集(除了空子集)
s1 - 1,相當於將s1中最右側的1變成0,同時將其右側全部變爲1.&s相當於把右側中不在s裏的1都去掉。
所以一次迭代相當於s1中最右側的1去掉,同時還原這個1右側的在s中的所有1,不影響被去掉的1的左側的1
那麼我們考慮每次迭代s1必然是減小的,且最後一定會減小到0,同時每次操作最多隻會刪除1個已有的1,可能還原出一堆1.
所以我們可以知道,對於s中的任意一個1,必然存在某一時刻,它被刪除了,而它左邊還沒開始刪,那麼此時它右邊的1都會被還原出來。
也就是說這個時刻相當於s中刪且只刪了這個1.
因爲對於任意一個1都存在這個時刻,因此我們可以知道s只去除1個1的子集肯定是枚舉完了。
然後我們用遞歸的思想理解一下。既然我們對於s只去除一個1的子集都枚舉完了,那麼我們在枚舉到這些只去除一個1的子集的時候,相當於將這個子集作爲新的s往下枚舉。
那麼我們又可以枚舉完這個子集所有隻去掉一個1的子集,也就相當於s只去掉兩個1的子集……
以此類推,我們可以知道最後我們可以枚舉完s去掉任意個1的子集。

常見的狀態變換

對s的第i位進行操作

\(k = 1 << (i - 1)\)即可得到一個第i位爲1,其他位爲0的二進制串,然後用這個二進制串和s進行操作即可。
比如:
判斷第i位是否爲1:s & k > 0 則第i位爲1,否則爲0
將第i位改爲1:s = s | k
將第i位改爲0:s = s & ~k (此時k的第i位爲0,所以可以強制s的第i位爲0,k的其他位爲1,因此不會影響s的其他位)

去掉s最右的1

s = s & (s - 1)
這裏類似於前面的子串枚舉了。
s - 1得到去掉最靠右的1且該1的右側全變爲1的二進制串。
就是上文中

所以我們可以知道,對於s中的任意一個1,必然存在某一時刻,它被刪除了,而它左邊還沒開始刪,那麼此時它右邊的1都會被還原出來。
也就是說這個時刻相當於s中刪且只刪了這個1.

這個任意1爲最右側1的情況。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章