• 反转(开关问题)


    参考《挑战程序设计》

    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;
    }
    
    加油啦!加油鸭,冲鸭!!!
  • 相关阅读:
    Segment Routing之IPv6 SR概述
    ping6 connect: Invalid argument
    glance image-list
    SRv6技术研究和组网设计
    physical_interface_mappings
    bond
    srv6 tools---SRext
    srv6
    8月18号
    8月17号
  • 原文地址:https://www.cnblogs.com/clarencezzh/p/10439309.html
Copyright © 2020-2023  润新知