大一寒假集训2020.1.3 //二进制枚举

大一寒假集训2020.1.3

异或运算

异或,英文为exclusive OR,缩写成xor
异或(xor)是一个数学运算符。它应用于逻辑运算。异或的数学符号为“⊕”,计算机符号为“xor”。其运算法则为:
a⊕b = (¬a ∧ b) ∨ (a ∧¬b)
如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。
异或也叫半加运算,其运算法则相当于不带进位的二进制加法:二进制下用1表示真,0表示假,则异或的运算法则为:0⊕0=0,1⊕0=1,0⊕1=1,1⊕1=0(同为0,异为1),这些法则与加法是相同的,只是不带进位。

对于异或一个非常重要的性质,对于一个值异或同一个值两次,则结果还是原值。

在c/c++中异或用^符号表示;
例如:
对于指定的两个数 A=60(0011 1100)
B=13(0000 1101)
执行一下操作 A^B=49(0011 0001)
就是对二进制每一位进行了一次异或操作,即非进位加法。
注意:0与任何数异或都是那个数本身。
teacher Li

题干意思是找出缺席的人。
分析可得一共要输入2n-1个名字。
直接的思路是把前N个名字读入,再读入后n-1个名字分别对每一位异或,即可得出正确结果。
很显然耗费很多时间,不出意外的TLE。
所以要对程序进行优化,可以先读入一个人的名字,之后的2
n-2个名字都和其异或,
这样只出现一次的名字就被记录下来。输出即可。

#include <bits/stdc++.h>
using namespace std;
char ch1[30], ch2[30];
int main()
{
    int N;
    int k=1;
    while (cin >> N)
    {
        cin >> ch1;       //先读入一个名字
        int l;
        for (int i = 0; i < 2 * N - 2; i++)
        {
            scanf("%s", ch2);
            l = strlen(ch2);     
            for (int j = 0; j < l; j++)        //因为是字符串,要对每一位进行异或;
            {
                ch1[j] = ch1[j] ^ ch2[j];
            }
        }
        cout << "Scenario"               //按照样例的格式输出。
             << " "
             << "#" << k << endl;
        cout << ch1 << endl<<endl;
        k++;
    }
    return 0;
}

Find different

找出哪个数只出现了一次
和上一题一样的思想,自定义一个ans=0,其余数都和他异或;
只出现一次的数就会留在ans上,直接输出ans就可以了。

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int N;
    while(scanf("%d",&N)!=EOF)         //注意:用scanf和printf会比用cin  cout快,不易超时。
    {
        int ans=0;
        int x;
        scanf("%d",&ans);
        N--;
        while(N--)
        {
            scanf("%d",&x);
            ans=ans^x;
        }
        printf("%d\n",ans);
    }
    return 0;
}

对于上面的两个例子,都是在一组数据中找出单次出现的数据,可以利用异或的性质
对同一个数异或两次保持不变,找到单独出现的数据。

二进制枚举

二进制枚举利用的是二进制下n位长度的数有2^n 个,而一个有n个元素的集合子集个数也为2^n个所以可以利用二进制的1,0和集合中的元素联系起来他可以实现组合也可以实现容斥
对一个二进制来说1代表取这个元素0代表不取这个元素,1和0所在的位置代表元素的位置,这样的思想在有时候给题目有了很大的方便举个例子如集合{a,b,c,d,e}
当二进制00000就代表什么都不取, 10000代表取a,01000代表取b,11000代 表取a,b如此
所以我们需要枚举的数量就是00000到11111,也就是0到1<<n位,<<代表左移操作

二进制枚举
一般适用于只有两种选择的情况,选或不选。

和为K–二进制枚举

用移位和&运算来实现每一位的选取,取或不取。
遍历数据,达到想要的目的,任取n个数。

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int N,k;
    cin>>N;
    cin>>k;
    int a[N];
    for(int i=0;i<N;i++)
    {
        cin>>a[i];
    }
    int flag=1;
    for(int i=0;i<(1<<N);i++)           //二进制枚举的一般格式
    {
        int sum=0;
        for(int j=0;j<N;j++)
        {
            if(i&(1<<j))             //用&来选择取或不取,可以1取0不取。
            {
                sum=sum+a[j];
            }
        }
        if(sum==k)
        {
            cout<<"YES"<<endl;
            flag=0;
            break;
        }
    }
    if(flag==1)
    {
        cout<<"NO"<<endl;
    }
    return 0;
}

陈老师加油-二进制枚举

这个题既可以用二进制枚举来表示取或不取,同时可以用深搜来写。

这是我写的二进制枚举

#include <bits/stdc++.h>
using namespace std;
int main()
{
    int t;
    cin >> t;
    int counter1, counter2, tmp;
    int sum = 0;
    for (int i = 0; i < (1 << 15); i++)
    {
        counter1 = 0;counter2 = 0;
        tmp = t;
        for (int j = 0; j < 15; j++)         //用二进制枚举就需要两个计数,同时满足条件sum++
        {
            if (i & (1 << j))
            {

                counter1++;
                tmp = tmp * 2;
            }
            else
            {
                counter2++;
                tmp = tmp - 1;
                if (tmp <= 0)
                    break;
            }
        }
        if (counter1 == 5 && counter2 == 10 && tmp == 0)
        {
            sum++;
        }
    }
    cout << sum << endl;
    return 0;
}

由于蒟蒻并不会深搜
这是大佬帮忙写的深搜

#include <bits/stdc++.h>
using namespace std;
int cnt = 0;
bool dfs(int t, int recross, int regas, int i)
{
    if (t <= 0 && i < 14)
    {
        return false;
    }
    if (i == 14 && t == 1 && recross == 1)
    {
        cnt++;
        return false;
    }
    if (i < 14 && t > 0 && regas > 0 && dfs(t * 2, recross, regas - 1, i + 1))
    {
        return true;
    }
    if (i < 14 && t > 0 && recross > 1 && dfs(t - 1, recross - 1, regas, i + 1))
    {
        return true;
    }
}
int main()
{
    int t;
    cin >> t;
    dfs(t, 10, 5, 0);
    cout << cnt << endl;
}

有兴趣的话,这有大佬的网站,可以去取取经。
倚栏听风

纸牌游戏-二进制-搜索

和前面的思路一样,用二进制枚举遍历各种情况把两个数的和满足条件的加进计数器就AC

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n;
    int luck;
    while(cin>>n>>luck)
    {
        int poke[20];
        for(int i=0; i<n; i++)
        {
            cin>>poke[i];
        }
        int ans;
        ans=0;                                  //特别注意一下,多组输入的时候把一些变量清空。
        for(int i=0; i<(1<<n); i++)
        {
            int sum=0;
            for(int j=0; j<n; j++)
            {
                if(i&(1<<j))
                {
                    sum+=poke[j];
                }
            }
            if(sum==luck)
            {
                ans++;
            }
        }
        cout<<ans<<endl;
    }
    return 0;
}

权利指数

难到蒟蒻的题来了。
我一直想用结构体把每个团体的数据记录在一起。
然后就TLE教我做人。
稍加优化,只用三个数组分别存票数,标记,还有权力指数。

#include <bits/stdc++.h>
using namespace std;
int T, s, sum;
int a[25], b[25], c[25];
int main()
{
    int x;
    cin >> T;
    for (int i = 1; i <= T; i++)
    {
        cin >> x;
        s = 0;
        for (int j = 0; j < x; j++)
        {
            scanf("%d", &a[j]);
            s = s + a[j];
        }
        memset(b, 0, sizeof(b));
        for (int j = 0; j < (1 << x); j++)
        {
            sum = 0;
            memset(c, 0, sizeof(c));
            for (int k = 0; k < x; k++)
            {
                if (j & (1 << k)) 
                {
                    sum += a[k];
                    c[k] = 1;
                }
            }
            if (sum > s / 2)                 //下面是判断条件,要同时满足缺其不可还有他被标记过。
            {
                for (int l = 0; l < x; l++)
                {
                    if (c[l] == 1)
                    {
                        if (sum - a[l] <= s / 2)
                        {
                            b[l]++;              //把他自己的权力指数++
                        }
                    }
                }
            }
        }
        for (int p = 0; p <= x - 2; p++)
            printf("%d ", b[p]);
        printf("%d\n", b[x - 1]);
    }
    return 0;
}

趣味解题

看着像数学题,奈何我数学太差。
只要把他们每个题AC的概率和WA的概率都存起来
用二进制枚举移位,得到他们AC题总数,和教练期望值比较,相等就把概率加进来。

#include <bits/stdc++.h>
using namespace std;
int main()
{
    double a[15], b[15], c[15], ac[15], wa[15],m,p;
    int x;
    int t,N;
    int counter;
    int i,j;
    cin >> t;
    while (t--)
    {
        cin >> N;
        for ( i = 0; i < N; i++)
        {
            cin >> a[i];
        }
        for ( i = 0; i < N; i++)
        {
            cin >> b[i];
        }
        for (i = 0; i < N; i++)
        {
            cin >> c[i];
        }
        cin >> x;
        m = 0;
        for ( i = 0; i < (1 << N); i++)
        {
            p = 1;
            counter = 0;
            for ( j = 0; j < N; j++)
            {
                wa[j] = (1 - a[j]) * (1 - b[j]) * (1 - c[j]);
                ac[j] = 1-wa[j];
                if (i & (1 << j))
                {
                    p = p * ac[j];
                    counter++;
                }
                else
                {
                    p = p * wa[j];
                }
            }
            if (x == counter)
            {
                m = m + p;
            }
        }
        printf("%.4lf\n", m);
    }
    return 0;
}

初学二进制枚举觉得很不好理解,其实后来发现他有点规律可循,
开始说的,一般只用于有两种情况的,数据较少的。
然后用的具体格式也大致一样。
两层循环 一个控制移位 ,一个负责控制条件
暴力遍历一遍就能得出最后结果。

发布了7 篇原创文章 · 获赞 0 · 访问量 1318

猜你喜欢

转载自blog.csdn.net/qq_34212975/article/details/103845449
今日推荐