</pre><pre name="code" class="cpp">#include<cstdio> #include<cstring> #include<iostream> using namespace std; void print(int n)//二进制观察,从左到右对应低位到高位 { for(int i=0;(1<<i)<=n;i++) if(n&(1<<i)) cout<<1; else cout<<0; cout<<endl; } int main() { int sum; while(scanf("%d",&sum)!=EOF) { cout<<"sum : ";print(sum); for(int i=sum;i;i=(i-1)&sum) print(i); cout<<endl; } return 0; }
关键代码
for(int i=sum;i;i=(i-1)&sum)
附:这段代码源于我在大白上看到的类似代码
对于集合sum。1表示该物品可取,0表示不可取,求出所有的可取状态。
上述代码不重复地取出了所有的子集!
下面给出i=(i-1)&sum这一表达式的证明
1.不重复性
证明: 设j=(i-1)∑( i 是已知的正确的状态)
由于取了&sum说明 j 也是正确的状态。
而 j是由(i-1)和某数按位与得到的,
则 j<i-1<i
得证(i-1)&sum能取出不重复的正确的状态来。
2.完整性
证明: 上面谈到了不重复性的证明,其实就是 i 的单调递减的证明
现在我们只需要证明 j=(i-1)&sum 得到的 j 是第一个比 i 小的状态。
设 状态 i 是 abcdef 10000 ;
i-1 是 abcdef 01111 ;
那么 (i-1)&sum会得到什么呢
我们将 i-1 分解来看 为 (abcdef 00000 + 000000 01111)&sum =(abcdef 00000&sum) + (000000 01111&sum)
= abcdef 00000 + (000000 01111&sum)
注意到 i&sum=i ( i 是已知的正确状态)
将 i 也分解来看看 (abcdedf 00000 + 000000 10000) &sum = abcdef 00000 + (000000 10000&sum)
假设 状态 s=abcdef 0****是第一个比 i 小的状态
可得:
① s>=j
② s<i<(abcdef 10000&sum) <= (abcdef 01111&sum) = (i-1)&sum = j 即 s<=j
由①②可得 s=j ;
得证 j 是第一个比 i 小的状态
得证 完整性
由1,2可以证明该算法的正确性 ,并可算出时间复杂度为( 2^n , n为可选物品数)。而所有的子集也为 2^n 个,因此该算法相当的好啊。
这神奇的取子集方法~~