[转]枚举子集

转自:https://www.cnblogs.com/jffifa/archive/2012/01/16/2323999.html

[技巧]枚举子集的飘逸写法

鸟泉学长告诉我的,今天想到了就顺便记上。

设S表示一个01状态集,那么它的所有非空子集x可以通过以下代码枚举。

for (int x = S; x; x = (x-1)&S)

简单说明下原理(证明以后补上?):

x = (x-1)&S实际上是把S中的0全部忽略,并不断减1的结果,比如S=1011,则x分别为:1011, 1010, 1001, 1000, 0011, 0010, 0001。忽略S中第二位的0其实就是111, 110, 101, 100, 011, 010, 001。

称S中的1所在位为有效位,0所在位为无效位,则x中的无效位必为0,有效位为0或1,比如S=1011,x=1001(有效位加下划线)。-1就是加上-1补码1111…,可以想成把无效位的1先加上去,比如x=1001变成1101,再加有效位的1。由于无效位加完肯定是1,会把有效位的进位“传递”下去,然后再位与S使得无效位变成0,实际就相当于有效位加上1111…,也就是有效位-1。

转自:https://blog.csdn.net/mtrix/article/details/61200302

        给集合里的元素一个顺序,那么就可以用整数表示集合,某一位为1表示对应元素被选取。

        设x为表示集合的整数,那么这个整数有如下性质:

         x的子集整数y在数值上不会比x大。因为x的子集y只是保留了x某些位置上的1,所以y总可以加上一个非负的整数z等于x,相当于把没选的1补上。

         根据这个性质可知,可以通过枚举所有比x小的数p并判断,p是否只含x对应位上的1,如果是则p是x的子集,否则不是。这样时间复杂度是严格的x。有没有更快的呢,有的。

上诉枚举p是通过减一操作,并且我们知道减一操作一定是正确的,那么在枚举的时候如何快速的去掉多余的状态,答案就是和x进行&(与)运算。与运算可以快速跳到下一个子集。

         &运算本质就是保留p在x对应位为1的数值,而根据二进制减法可知减一操作都是把p最低位的1消去,在那一位后全补上1,如果在x对应位为0的地方产生了1其实是无效的,

后续的减一操作也会把它消掉,所以直接&运算可以快速去掉多余的状态。时间复杂度是x的子集数。

原文地址:https://www.cnblogs.com/hehe54321/p/8696409.html