枚举到中的每个数字,然后将数字转化为二进制,
如果的第位是,表示集合包含;
如果的第位是,表示集合不包含。
下面这段代码可以从小到大枚举的所有子集
void solve(int n) { for (int i = 0; i < 1 << n; i++) { printf("正在枚举集合i i=%d\n", i); for (int j = 0; j < n; j++) { if (i >> j & 1) { printf("集合i包含数字j i=%d j=%d\n", i, j); } else { printf("集合i不包含数字j i=%d j=%d\n", i, j); } } } }
枚举大小为的子集,初始化为最小的满足条件的集合。
计算x = lowbit(i)
和y = i + x
。
由于进位,y
的二进制可能包含比i
更少的1
。
(i & ~y)
可以得到除了x
所有进位的位置,显然这些位置一定是连续的1
。
接下来除以x
,除以2
(也可以写成右移1
位)可以删去所有连续的1
右侧的0
。
再和y
按位或,便可以得到下一个,恰好包含k
个1
的子集。
下面这段代码可以从小到大枚举的所有大小为的子集
void solve(int n, int k) { for(int i = (1 << k) - 1; i < (1 << n);) { // ... i int x = i & -i, y = i + x; i = (((i & ~y) / x ) >> 1) | y; } }
11001110 i
00000010 i & -i = lowbit(i)
11010000 i + lowbit(i) = y
00101111 ~y
00001110 i & ~y
00000111 (i & ~y) / x
00000011 ((i & ~y) / x) >> 1
11010011 ((i & ~y) / x) >> 1 + y
初始i = x
,之后每次i = (i - 1)
但是这个的结果可能包含x
没有的元素
所以执行i = (i - 1) & x
即可枚举所有子集,
最后结束条件是i == 0
,如果i == 0
就推出。
下面这段代码可以从大到小枚举所有的子集
void solve(int n) { for (int i = x;; i = (i - 1) & x) { // ... i if (i == 0) { break; } } }
枚举超集和枚举子集思路基本一样,只是把变成了,把按位与变成了按位或。
下面这段代码可以从小到大枚举所有的超集
void solve(int n, int x) { for (int i = x; i < 1 << n; i = (i + 1) | x) { // ... i } }
Luogu P1036
枚举大小为的所有子集。
uoj 300
Lucas定理,然后枚举子集。
https://atcoder.jp/contests/abc159/tasks/abc159_e