反转(开关问题)

参考《挑战程序设计》

https://blog.csdn.net/codeswarrior/article/details/80228454

https://blog.csdn.net/sky_zdk/article/details/60320721

集合与二进制

在程序中表示集合的方法有很多种,当元素数比较少时(集合元素的个数不超过32位时可以用int,不超过64时可以用long long),像这样用二进制码来表示比较方便。集合{0,1,2…n-1}的子集可以用以下方式编码成整数。 
这里写图片描述 

举一个比较直观的例子:

比如一个集合(5,8,3,6,78,45,33,22),我们可以用一个n位的2进制数表示这个集合。

比如11111111代表这个全集,而10100000代表(5,3)这个子集(二进制中1代表这个子集中有全集中相应位置的数字)

像这样表示后,一些集合运算可以对应地写成如下方式:(全集按6个元素)

空集......................................................................0

只含有第i个元素的集合{i}.....................................1<<i   (1<<2=4(10进制)=000100(2进制),1<<4=010000)

只含有全部n个元素的集合{0,1,2,....n-1}...............(1<<n)-1  (1<<6=1000000     1<<6-1=0111111)

判断第i个元素是否属于集合S...............................if(S>>i&1) 

向集合中加入第i个元素SU{i}................................S|1<<i

从集合中去除第i个元素S{i}..................................S&~(1<<i)

集合S和T的并集....................................................S|T

集合S和T的交集....................................................S&T

将集合{0,1,2,....n-1}所有子集枚举出来,按照这个顺序进行循环的话,S便会从空集开始循环,然后按照{0}、{1}、{0,1}、…、{0,1,…,n-1}的升序枚举出来。

for(s=0;s<1<<n;s++)

{//接下来的操作。

}

枚举某个集合(这个集合本身是别人的子集 比如0111000)的子集。这里sup 是一个二进制码,其本身也是某个集合的子集。例如给定了01101101 这样的集合,要将01100000 或者00101101 等子集枚举出来。前面是从0 开始不断加1 来枚举出了全部的子集。此时,sub+1 并不一定是sup 的子集。而(sub+1)&sup 虽然是sup 的子集,可是很有可能依旧是sub,没有任何改变。所以我们要反过来,从sup 开始每次减1 直到0 为止。由于sub-1 并不一定是sup 的子集,所以我们把它与sup 进行按位与&。这样的话就可以将sup 所有的子集按照降序列举出来。(sub-1)&sup 会忽略sup 中的0 而从sub 中减去1。

   int sub = sup;
    do {
        // 对子集处理
       // print_subset(n,sub);
        sub = (sub-1) & sup;
    } while(sub != sup); // 处理完之后 会有 -1&sup = sup

 枚举{0,1,2....n-1}所包含的所有大小为k的子集

 int comb = (1<<k)-1;
    while (comb< 1<<n)
    {
       // print_subset(n,comb);
        //这里进行针对组合的处理
        int x=comb&(-comb), y=comb+x;
        comb=((comb&~y)/x>>1)|y;
    }

按照字典序的话,最小的子集是(1<< k)-1,所以用它作为初始值。现在我们求出comb其后的二进制码。例如0101110之后的是0110011,0111110之后的是1001111。下面是求出comb下一个二进制码的方法。

(1)求出最低位的1开始的连续的1的区间。
(0101110 -> 0001110)
(2)将这一区间全部变为0,并将区间左侧的那个0变为1。
(0101110 -> 0110000)
(3)将第一步里取出的区间右移,直到剩下的1的个数减少了一个。
(0001110 -> 0000011)
(4)将第二步和第三步中的结果按位取或(0110000|0000011 = 0110011)

对于非零的整数, x&(-x)的值就是将其最低位的1独立出来后的值,这部部分内容在树状数组部分有详细讲解,这里就不赘述了。

将最低位的1取出后,设它为x。那么通过计算y = comb + x,就将comb从最低位的1开始的连续的1都置零了。我们来比较一下~y和comb。在comb中加上x后没有变化的位,在~y中全都取相反的值。而最低位1开始的连续区间在~y中依然是1,区间左侧的那个0在~y中依然也是0.于是通过计算z = comb & ~y 就得到了最低位1开始的连续区间。比如:如果comb = 0101110,则 x = 0000010,y = 0110000,z = 0001110.
同时,y也恰好是第二步要求的值。那么首先将z不断右移,直到最低位为1,这通过计算z/x即可完成。这样再将z/x右移1位,就得到了第三步要求的值。这样我们就求得了comb之后的下一个二进制列。因为是从n个元素的集合中进行选择,所以comb的值不能大于等于
1*2^n,如此一来,就完成了大小为k的所有子集的枚举。


代码如下:

#include<cstdio>
#include<iostream>
using namespace std;
int a[10];
//打印集合元素
void print_subset(int n,int b)  //n位的集合,b状态打印
{
    printf("{ ");
    for (int i=0;i<n;i++)
        if (b&(1<<i)) printf("%d ",a[i]);
    printf("}
");
}

void p_s(int n)
{
    for (int i=0;i<(1<<n);i++) print_subset(n,i);  //枚举所有状态
}

int main()
{
    int n;
    scanf("%d",&n);
   //枚举该集合的所有子集 for (int i=0;i<n;i++) a[i]=i; p_s(n);
  //只含有第i个元素的集合{i} print_subset(n,1<<2);
  //含有n个元素的集合 print_subset(n,(1<<n)-1);
  //从集合中去除第i个元素S{i} print_subset(n,((1<<n)-1)&~(1<<1));
  //求两个集合的交集 print_subset(n,1<<1|1<<2); cout<<"子集的子集:" <<endl; int sup=(1<<n)-3; int sub = sup; do { // 对子集处理 print_subset(n,sub); sub = (sub-1) & sup; } while(sub != sup); // 处理完之后 会有 -1&sup = sup cout<<"大小为k的大小:" <<endl; int comb = (1<<3)-1; while (comb< 1<<n) { print_subset(n,comb); //这里进行针对组合的处理 int x=comb&(-comb), y=comb+x; comb=((comb&~y)/x>>1)|y; } return 0; }

  

如果一个元素只有两种状态,枚举所有状态的代码如下。

#include<iostream>
#include<string.h>
using namespace std;
//flip第一行可以枚举所有状态
/*
i:0  0 0 0 0
i:1  1 0 0 0
i:2  0 1 0 0
i:3  1 1 0 0
i:4  0 0 1 0
i:5  1 0 1 0
i:6  0 1 1 0
i:7  1 1 1 0
i:8  0 0 0 1
i:9  1 0 0 1
i:10  0 1 0 1
i:11  1 1 0 1
i:12  0 0 1 1
i:13  1 0 1 1
i:14  0 1 1 1
i:15  1 1 1 1
*/
int main(){
    int flip[4][4];
    int N=4;
    for(int i=0; i<1<<N; i++)//1<<N: 2^n
       {
           cout<<"i:"<<i<<"  ";
           memset(flip,0,sizeof(flip));
           for(int j=0; j<N; j++)
           {
               flip[0][N-j-1]=i>>j&1;
           }
           for(int j=0; j<N; j++) {
              cout<<flip[0][N-j-1]<<" ";
           }
           cout<<endl;
       }
    return 0;
}
加油啦!加油鸭,冲鸭!!!
原文地址:https://www.cnblogs.com/clarencezzh/p/10439309.html