集合与二进制
前面的代码里(【开关问题】POJ 3276 3279 3185 1222)可以看到一些为了枚举所有状态用到了集合的整数表现与位操作。
虽然位操作枚举比较简单但是这种方法只能在元素比较少的情况下(20 个左右)使用。
假设集合有 
举个例子,8 个元素编号依次为 
集合的操作
像这样表示之后,一些集合运算就可以对应地写成如下方式:
| 操作 | 方式 | 
|---|---|
| 空集 | 
0 | 
| 全集 | 
(1<<N)-1 | 
| 只含有第  | 
1<<i | 
| 判断第  | 
if(S>>i & 1) | 
| 向集合中加入第  | 
S|1<<i | 
| 从集合中去掉第  | 
S&~(1<<i) | 
| 集合  | 
S|T | 
| 集合  | 
S&T | 
实例
枚举所有子集
要将集合 
for(int S=0; S<1<<N; ++S) {// ...}
测试代码
void print_set(int Subset, const char *uSet) {
	int N = strlen(uSet);
	printf("{");
	for(int i=0; i<N; ++i)
		if(Subset&1<<i) printf("%c,", uSet[i]);
	if(Subset != 0) printf("\b");
	printf("}\n");
}
int main() {
	char Set[] = "ABC";
	puts("集合 {A, B, C} 的所有子集:");
	int N = strlen(Set);
	for(int S=0; S<1<<N; ++S)
		print_set(S, Set);
}
输出结果
集合 {A, B, C} 的所有子集:
{}
{A}
{B}
{A,B}
{C}
{A,C}
{B,C}
{A,B,C}
枚举某个集合 sup 的所有子集
int sub = sup;
do {
	// ...
	sub = (sub-1) & sup;
} while(sub != sup); // 当 sub = 0 的时候,有 sub = (-1 & sup) = sup
测试代码
int main() {
	char Set[] = "ABCDEF";
	int sup = 11; // 001011
	puts("sup 集合元素为:");
	print_set(sup, Set);
	puts("其所有子集为:");
	int sub = sup;
	do {
		print_set(sub, Set);
		sub = (sub-1) & sup;
	} while(sub != sup); // 当 sub = 0 的时候,有 sub = (-1 & sup) = sup
	return 0;
}
输出结果
sup 集合元素为:
{A,B,D}
其所有子集为:
{A,B,D}
{B,D}
{A,D}
{D}
{A,B}
{B}
{A}
{}
枚举所有大小为 k 的子集
int comb = (1<<k) - 1;
while(comb < 1<<N) {
	// ...
	int x = comb & -comb, y = comb + x; // x 为独立最低位的 1,y 为从最低位的 1 开始连续的 1 都置为 0
	comb = ((comb & ~y) / x >> 1) | y;
}
测试代码
int main() {
	char Set[] = "ABCDEF";
	int N = strlen(Set);
	int K = 4; // 枚举长度为 4 的所有集合
	int S = (1<<K) - 1;
	while(S < 1<<N) {
		print_set(S, Set);
		int x = S & -S, y = S + x; // x 为独立最低位的 1,y 为从最低位的 1 开始连续的 1 都置为 0
		S = ((S & ~y) / x >> 1) | y;
	}
}
输出结果
{A,B,C,D}
{A,B,C,E}
{A,B,D,E}
{A,C,D,E}
{B,C,D,E}
{A,B,C,F}
{A,B,D,F}
{A,C,D,F}
{B,C,D,F}
{A,B,E,F}
{A,C,E,F}
{B,C,E,F}
{A,D,E,F}
{B,D,E,F}
{C,D,E,F}