大一寒假训练:二进制枚举

一、介绍:

如果某一事件情况只有两种,例如棋子翻转问题,除了黑就是白,那么我们就可以利用二进制来对事件的所有情况进行枚举。
通常我们设 1 为选取状态, 0 为未选取状态
———————————————————————————————————————————————
先介绍位运算
位运算是对二进制的每一位进行计算,所以每一位只有 0或 1两种可能

四种常用的位运算符:与&、或|、异或^, 按位取反~;

1.与运算:两者都为 1时,结果即为1,否则为0。–有0出0。(同为1则为1,其余为0)
2.或运算:两者都为 00时,结果即为0,否则为1。–有1出1。( 有1即1)
3.异或运算:是两者同为 0 或1 时,结果即为0,否则为1。相同出1 ,相异出0;(“半加”不进位,舍去进位)
4.按位取反:将该二进制数上每一位取反,1变0,0变1;

位运算的两种操作,左移<<右移>>

对于A << B,表示把A转化为二进制后向左移动B位(在末尾添加B个0, 即变成A乘以2的B次方)。
如:1<<5 意为:二进制数100000(1后面5个0)
对于A >> B,表示把A转化为二进制后向右移动B位(删除末尾的B位, 即变成A除以2的B次方)。
如:二进制数10000>>5 意为:二进制数1(删了5个0)

注意:int型最大为:(1<<31)-1;
long long型最大:((long long)1<<63)-1


关于异或运算:^
1.异或运算的作用
  参与运算的两个值,如果两个相应bit位相同,则结果为0,否则为1。
  即:
  0^0 = 0,
  1^0 = 1,
  0^1 = 1,
  1^1 = 0
2.按位异或的3个特点:
(1) 0^ 0=0,0^1=1 0异或任何数=任何数

(2) 1^ 0=1,1^1=0 1异或任何数=任何数取反

(3 任何数异或自己=把自己置0

利用这三个特点中的第一个和第三个,可以解决下面的第一题和第二题。
3.计算机中异或的操作是对于整型的操作,double不可以

二、专题练习&题解:

*如果n很大还是不要用二进制枚举,必定TLE。

1.Find different

因为要么是偶数次,要么一次,所以可以用异或来写(偶数次这点很重要!)。tips:1^2==2 ^1,所以不用在乎顺序怎样,只要是出现了偶数次的数进行异或运算,都得0;所以最后ans就等于那个只出现了一次的数。

#include <bits/stdc++.h>
using namespace std;
int main()
{
    int n,x,ans=0;
    while(cin>>n)
    {
        ans=0; //记得先赋初值为0
        while(n--)
        {
            scanf("%d",&x);
            ans=ans^x;
        }
        printf("%d\n",ans);
    }
    return 0;
}

2.teacher Li

和上题差不多,不过换成了求只出现过一次的字符串。字符串如和进行异或运算?每个元素一个一个比就行了(因为可以转化成ASCII值,所以可以进行)。

c_str()函数返回一个指向正规C字符串的指针, 内容与本string串相同.
C++中 c_str( )主要用法就是这是为了与c语言兼容,在c语言中没有string类型,故必须通过string类对象的成员函数c_str()把string 对象转换成c中的字符串样式。

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int k=0,n;
    string str,ans;
    ios::sync_with_stdio(false);  //取消cin、cout和stdio的关联,加快cin、cout的执行速度
    while(cin>>n)
    {
        cin>>ans;  //直接输入第一个先做为answer,往后比
        for(int i=1;i<=2*(n-1);i++)
        {
            cin>>str;
            for(int j=0;j<max(str.length(),ans.length());j++)  //这里注意要取最长的字符串长度
                ans[j]=ans[j]^str[j];  //一个元素一个元素的运算,出现偶数次的最后得0,剩下单数次的
        }
        k++;
        printf("Scenario #%d\n",k);
        printf("%s\n\n",ans.c_str()); //c_str() 以 char* 形式传回 string 内含字符串
    }
    return 0;
}

3.和为K–二进制枚举

这题展示了二进制枚举的主要框架。

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int n,k;
    while(~scanf("%d %d",&n,&k))
    {
        int str[n];
        int f=1;
        for(int i=0;i<n;i++)
            cin>>str[i];
        for(int i=0;i<(1<<n);i++)  //步骤一:来一串长度为n的二进制数串
        {
            int t=0;  //注意,在多组输入中,计数器需要在这里清零!
            for(int j=0;j<n;j++)  //步骤二:“扫描”看看各种可能中是1是0的情况
            {  //通常一个if表示正面情况
                if(i&(1<<j))  //如过j=0,则数串第一个为1;j=1,则数串第二个为1(从右往左),以此类推
                    t=t+str[j];
            }
            if(t==k)  //另一个if表示反面情况
            {
                cout<<"Yes"<<endl;
                f=0;break;
            }
        }
        if(f==1)
            cout<<"No"<<endl;
    }
    return 0;
}

4.陈老师加油-二进制枚举

首先分析题目,路过加油站翻倍,路过十字路口减一,最后一次是路过十字路口,即前14次情况未知。设加油站情况为1,十字路口为0,应用上题所说的框架枚举。

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int ans=0;
    int num_0=0,num_1=0,num,num1;//因为每一种情况的讨论都需要相同的初始条件,所以定义了一个num1
    cin>>num;
    for(int i=0;i<(1<<14);i++)
    {
        num_0=0;
        num_1=0;
        num1=num;
        for(int j=0;j<14;j++)
        {
            if(i&(1<<j))
            {
                num_1++;
                num1*=2;
            }
            else
            {
                num_0++;
                num1--;
            }
        }
        if(num_1==5 && num_0==9 && num1==1) //在每种情况最后判断是否满足题意
            ans++;
    }
    cout<<ans;
    return 0;
}

5.纸牌游戏-二进制-搜索

同上类型

#include <bits/stdc++.h>
using namespace std;

int main()
{
int n,m;
scanf("%d %d",&n,&m);
    int str[n],t=0,ans=0;
    for(int i=0;i<n;i++)
        cin>>str[i];
    for(int i=0;i<(1<<n);i++)
    {
        t=0;
        for(int j=0;j<n;j++)
        {
            if(i&(1<<j))
            {
                t+=str[j];
            }
        }
        if(t==m)
            ans++;
    }
    cout<<ans<<endl;

    return 0;
}

6.权利指数

“'关键加入者’的条件是:在其所在的获胜联盟中,如果缺少了这个小团体的加入,则此联盟不能成为获胜联盟”,那我们可以先取任意小团体票数加和,让这个值<=总票数的一半,并都给这些团体一个标志表示不可能再作为关键加入者了。再从剩余的团体中取一个加进去,如果这个值>总票数的一半,则是。

#include <bits/stdc++.h>
using namespace std;

int n,str[21],ans[21],f[21],total,tmp;
int main()
{
    int t;  cin>>t;
    while(t--)
    {
        cin>>n;
        total=0;  //因为多组输入,所以要记得清零
        for(int i=0;i<n;i++)
        {cin>>str[i]; total+=str[i];} //统计总票数
        memset(ans,0,sizeof(ans));
        for(int i=0;i<(1<<n);i++)
        {
            tmp=0;  //计数器清零
            memset(f,0,sizeof(f));
            for(int j=0;j<n;j++)
            {
                if(i&(1<<j))
                {
                    tmp+=str[j];  //任意团体票数累加
                    f[j]=1;
                }
            }
            if(tmp<=total/2) 
                {
                    for(int s=0;s<n;s++)
                    {
                        if(tmp+str[s]>total/2 && f[s]==0)
                            ans[s]++;
                    }
                }
        }
        for(int i=0;i<n-1;i++)
            printf("%d ",ans[i]);
        printf("%d\n",ans[n-1]);
    }
    return 0;
}

7.趣味解题

定义一个变量final_p作为最终要输出的概率,跟随程序运行而改变。然后计算所有m(变量acnum表示)道对y(即n-acnum)道错(总共n题)的总概率。当acnum==输入的x时,记录这种情况的概率并输出。

#include <bits/stdc++.h>
using namespace std;

int main()
{
    double a[15],b[15],c[15],p=1.0,candop/*能做出来的概率*/,cantp/*做不出来的概率*/;
    int t,n,x,acnum=0;//计数做出的题目数
    cin>>t;
    while(t--)
    {
        cin>>n;
        for(int i=0;i<n;i++)
            cin>>a[i];
        for(int i=0;i<n;i++)
            cin>>b[i];
        for(int i=0;i<n;i++)
            cin>>c[i];
        cin>>x;
        double final_p=0;
        for(int i=0;i<(1<<n);i++)
        {
            acnum=0;  //保证每种情况有相同的初始条件
            p=1.0;  
            for(int j=0;j<n;j++)
            {
                if(i&(1<<j))
                {
                    candop=a[j]+(1-a[j])*b[j]+(1-a[j])*(1-b[j])*c[j];
                    p=p*candop;
                    acnum++;
                }
                else
                {
                    cantp=1-(a[j]+(1-a[j])*b[j]+(1-a[j])*(1-b[j])*c[j]);
                    p=p*cantp;
                }
            } 
            if(x==acnum)
                final_p+=p;
        }
        printf("%.4lf\n",final_p);
    }
    return 0;
}

  • 顺便在@Havoc.Wei的博客上看到了一个名字有趣的题目:

四糸乃买花-二进制枚举

#include<bits/stdc++.h>
using namespace std;
int cntf,tw,ans;
int main() {
    int n,w;  cin>>n;
    int str[n];
    for(int i=0;i<n;i++)
        cin>>str[i];
    cin>>w;
    for(int i=1;i<(1<<n)-1;i++)
    {
        cntf=0;
        tw=w;
        for(int j=0;j<n;j++)
        {
            if(i&(1<<j))   //开始憨批地写的if(i&(1<<j)&&tw>=str[j])意图是钱够才能买
            {				//后来才想起取到1就是买的情况,我不能人为限制它(/捂脸),要把这个条件反到
                tw=tw-str[j];   //下面那个if里......
                cntf++;
            }
        }
        if(cntf!=0&&cntf%4==0&&tw%4==0&&cntf!=n&&tw>=0)
           ans++;
    }
    cout<<ans;
    return 0;
}

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值