一、介绍:
如果某一事件情况只有两种,例如棋子翻转问题,除了黑就是白,那么我们就可以利用二进制来对事件的所有情况进行枚举。
通常我们设 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;
}