我读
for ( x = y; x > 0; x = ( y & (x-1) ) )
生成位掩码y的所有子集。
这个迭代如何工作?任何直观的解释?
来源:http://codeforces.com/blog/entry/45223
请参阅次优解决方案部分。
直觉:作为一个数字,位掩码y
不能超过y
子集。因此,通过倒数x
,您可以保证通过位掩码击中y
的每个子集。但是这会产生很多重复。想想1101
。如果你倒数并用y
掩盖,那么序列就会消失。 1101
,1100
,1001
,1000
,1001
,1000
等。通过为x
指定屏蔽操作的结果,可以跳到最后一次出现。
证明:这导致通过归纳进行简单的证明。显然,对于长度为1的位串,此过程有效。只有两个子集1
和0
按顺序发出。
现在假设此过程适用于长度为N
的位串。假设Z
是长度为N
的位串。如果你创建了bitstring 0Z
,那么你遵循与Z
相同的顺序,因为减法不会打开更高阶的位。如果你创建了bitstring 1Z
,那么会发生以下情况:对于第一个2^nnz(Z)
步骤,遵循原始的Z
序列,前面加上1
。并且对于最后的2^nnz(Z)
步骤,遵循原始的Z
序列,前面加上0
。由于该过程访问较小序列的每个元素两次,第一次前置1
,0
第二次,我们得出结论,该过程发出1Z
的每个子集。
总之,我们看到该过程适用于所有位串。
这里使用的第一个简单事实是,如果我们说,取值7
(二进制中的111
)并开始重复递减(一直到0
),我们将通过二进制表示
111, 110, 101, 100, 011, 010, 001, 000
它以一种相当明显的方式表示原始3组的所有可能子集。
第二个事实是二进制“递减x
”(“从x
减去1”)意味着:从最不重要(最右边)一个向左反转所有位x
到第一个(包括) 1
代表x
。 “向左”在这里意味着“在增加位重要性的方向上”。
EG
00001000 - 1 = 00000111, i.e. we invert the `1000` tail
01010101 - 1 = 01010100, i.e. we invert just the `1` tail
10000000 - 1 = 01111111, i.e. we invert the whole thing
and so on
递减操作“关闭”二进制表示中的最低有效1
位,并“打开”其右侧的所有零位。
现在,第三个事实是,在你的情况下,1
的x
位总是1
的y
位的子集,因为我们从x = y
开始并在每次迭代时执行x = (whatever) & y
。
当我们做x - 1
时,我们“关闭”(设置为0
)1
中最低的x
,并且“从1
开始”(设置为0
)所有x
从最低的1
到右边。
当我们在& y
跟随x = (x - 1) & y
时,我们在y
“关闭”x
的一些原始位并在y
“打开”x
的所有较低的原始位。
在这一点上,已经很明显这个操作只是x
的“掩盖减量”:通过做x = (x - 1) & y
我们简单地减去x
的值,假设只有y
掩盖的位形成x
的值,而所有其他位只是可以忽略不计的“填充位”。
要通过递减7
绘制与上述示例的平行线,y
的初始值可能看起来像10010100
。操作x = (x - 1) & y
将此值视为“分布式7”(非正式地说)。 x
将通过以下值继续
1..1.1.., 1..1.0.., 1..0.1.., 1..0.0.., 0..1.1.., 0..1.0.., 0..0.1.., 0..0.0..
其中.
指定x
的“pading”位,它们并不真正参与这个“蒙面减量”操作(实际上它们将是0
)。请注意与7
的原始示例的相似性。