状态压缩:使用二进制数表示一个集合的情况,第i位为1表示第i元素在集合中,为0表示不在集合中。
已知i表示的集合是s表示的集合的子集,枚举s的所有子集i可以写为
cpp
for(int i = s; i != 0; i = (i-1)&s){}
证明:每次循环执行i = (i-1)&s可以枚举s表示集合的所有子集i
使用数学归纳法:
- 证明:当s只有1位为1时,每次循环执行i = (i-1)&s可以枚举s表示集合的所有子集i
s只有1位为1,设s为 2 n 2^n 2n,那么s = 10...(n个0)...0
第一次循环开始,i为s,i = 10...(n个0)...0
而后求i = (i-1)&s
i-1 = 01...(n个1)...1
(i-1)&s = 0...(n+1个0)...0
即i = 0...(n+1个0)...0
i已经取到了s的所有子集。 - 已知s有k位1时,每次循环执行i = (i-1)&s可以枚举s表示集合的所有子集i,证明:当s有k+1位1时,每次循环执行i = (i-1)&s可以枚举s表示集合的所有子集i。
因为s有k+1位1,所以s的1的位数大于等于2。
设s的形式为10...(a个0)...01X...X,X可以是1也可以是0。从第2个1开始,已知到右端共有k位1。当i中1的位数大于等于2时,在执行i = (i-1)&s后,最高位的1都不会变化,会变化的只有1X...X,也就是有k位1的部分。
已知一个数有k位1时,每次循环执行i = (i-1)&s可以枚举它表示集合的所有子集,把对1X...X枚举到的每个子集加上最高位1表示的元素,就得到了s的包含最高位1表示的元素的所有子集。
不断执行i = (i-1)&s枚举1X...X的所有子集后,1X...X会变为0, 即i=10...0。
再次执行i = (i-1)&s,i = 0...(a+1个0)...1X...X,该数字包含k位1,每次循环执行i = (i-1)&s可以枚举它表示集合的所有子集,对1X...X枚举到的每个子集为s不包含最高位1表示的元素的所有子集。
因此,当s有k+1位1时,每次循环执行i = (i-1)&s可以枚举s表示集合的所有子集i。