关于状压DP枚举子集的方法与理解

题外话:

刚才发现自己已经不记得如何枚举一个状压集合的子集(因为之前本身就没有怎么理解枚举子集的方法完全就是背下来的所以忘掉很正常),所以写下这篇博客做个提醒或者叫做警示吧,很多东西还是要理解透彻不然会吃亏的。

希望这篇博客可以对博友们一些帮助,当然如果有错欢迎指出。


我们现在要枚举状压集合 S 的子集,代码实现就是:

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

其中 S 1 就是我们枚举得到的子集, S 2 S 1 S 内的补集,即 S 1 S 2 = S


赘述如下:

现在来讲一讲为什么是这样的一个枚举方法,先让我们来举一个例子来模拟一下。

假设我们当前要枚举的是 ( 10110 ) 2 的子集(子集仍然用 S 1 表示):

S 1 = ( 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 了。

猜你喜欢

转载自blog.csdn.net/yanzhenhuai/article/details/81572028
今日推荐