acm新手小白必看系列之(4)——超级简单的二进制枚举精讲及例题
- 二进制操作(事前须知)
(还有排列组合知识要知道,比如n个数的子集个数是2的n次幂)
算数位运算:
1、与(&):
对于指定的两个数A=60(0011 1100)
B=13(0000 1101)
执行一下操作 A&B=12(0000 1100)
就是对二进制每一位进行了一次与操作,同为1,结果为1,否则为0
2、或(|):
对于指定的两个数A=60(0011 1100)
B=13(0000 1101)
执行一下操作 A|B=61(0011 1101)
就是对二进制每一位进行了一次或操作,同为0,结果为0,否则为1
3、非 按位取反(~):
对于指定的一个数A=60(0011 1100)
执行以下操作 ~A=195(1100 0011)//此处只举例了8位,int是32位的,且第一位是符号位
就是对二进制每一位进行了一次取反操作,若二进制数位0,则变成1,否则变成0.
4、异或运算(看黑体字就够了)
异或,英文为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)
就是对二进制每一位进行了一次异或操作,即非进位加法
- 好了,下面开始准备WA
1.超级简单的一道题((当作例题)
给出长度为n的数组,求能否从中选出若干个,使他们的和为K.如果可以,输出:Yes,否则输出No
Input
第一行:输入N,K,为数组的长度和需要判断的和(2<=N<=20,1<=K<=10^9)
第二行:N个值,表示数组中元素的值(1<=a[i]<=10^6)
Output
输出Yes或No
Sample Input
5 13
2 4 6 8 10
Sample Output
No
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int main()
{
int f=1,n,k,a[21];
while(~scanf("%d %d",&n,&k))
{
for(int i=0; i<n; i++)
{
scanf("%d",&a[i]);
}
//二进制枚举的核心来了
for(int i=0; i<(1<<n); i++)//第一个循环i<(1<<n)枚举出n个数每个取与不取的情况,1取,0不取.so,要来个长度为n的0.1串表示取与不取的情况。
{
int sum=0;
for(int j=0; j<n; j++)
//第二个for循环是根据第一个循环枚举的0.1串长度来对这次枚举的0,1串判断,各个位上是否为1,就是是否取此数
{
if(i&(1<<j))//1<<j是得到某个字符串上只有一个位置为1的数,这个数再进行&,&操作的目的是与第一次枚举的i同为1的才得到1,才可以进入下面的if,也就是取到此数。然后第二个循环就可以判断出枚举到i这个01串哪些位置上为1
{
sum+=a[j];
}
}
//以上及是核心步骤,二进制的精华模板
if(sum==k)
{
cout<<"Yes"<<endl;
f=0;
break;
}
}
if(f==1)
{
cout<<"No"<<endl;
}
}
return 0;
}
2.扑克练习
给你一些扑克,每张都对应一个点数,分别对应1-13,K 就是13;J 是11;Q是12;
现在想从这些扑克牌中取出一些牌,让这些牌的点数的和等于一个幸运数值P,问有多少种方案?
Input
输入数据第一行为n和p,分别代表n张扑克牌和幸运数(1<=n<=20,p<=260)
接下来是这n张牌的点数; 1<=点数<=13;
Output
输出能得到P 的方案数?
Sample Input
5 5
1 2 3 4 5
Sample Output
3
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int main()
{
int a[21],n,p,ans,sum;
while(~scanf("%d%d",&n,&p))
{
for(int i=0; i<n; i++)
{
scanf("%d",&a[i]);
}
ans=0;
for(int i=0; i<(1<<n); i++)//套用
{
sum=0;
for(int j=0; j<n; j++)
{
if(i&(1<<j))//套用
sum+=a[j];
}
if(sum==p)//在第一层里面判断点数之和
ans++;
}
printf("%d\n",ans);
}
return 0;
}
- 有信心了哈。。。。
3.老师开车
陈老师经常开车的行走,假设刚开始油箱里有T升汽油,每看见加油站陈老师就要把汽油的总量翻倍(就是乘2);每看见十字路口气油就要减少1升;最后的时候陈老师的车开到一个十字路口,然后车就没油了------就熄火了,陈老师好痛苦啊~~~!
然后他就开始回忆,一路上一共遇到5个加油站,10个十字路口,问造成这种惨烈的境遇有多少种可能?
Input
输入一个T ,(1<=T<=100);
Output
输出可能的方案数。
Sample Input
1
Sample Output
10
#include <bits/stdc++.h>
using namespace std;
int main()
{
int t,tmp,ans,k1,k2,i,j;
cin>>t;
ans=0;
for(i=0; i<(1<<15); i++)//n可以理解为变成了15
{
tmp=t;
k1=k2=0;
for(j=0; j<15; j++)//套用模板
{
if(i&(1<<j))//套用模板
{
tmp=tmp*2;//加油
k1++;//计数加油站
}
else
{
tmp--;//十字路口减一
k2++;//计数十字路口
if(tmp==0)break;//判断条件没有油了
}
}
if(k1==5&&k2==10&&tmp==0)//判断条件
ans++;
}
printf("%d\n",ans);
return 0;
}
4.进阶题目权力的指数
在选举问题中,总共有n个小团体,每个小团体拥有一定数量的选票数。如果其中m个小团体的票数和超过总票数的一半,则此组合为“获胜联盟”。n个团体可形成若干个获胜联盟。一个小团体要成为一个“关键加入者”的条件是:在其所在的获胜联盟中,如果缺少了这个小团体的加入,则此联盟不能成为获胜联盟。一个小团体的权利指数是指:一个小团体在所有获胜联盟中成为“关键加入者”的次数。请你计算每个小团体的权利指数。
Input
输入数据的第一行为一个正整数T,表示有T组测试数据。每一组测试数据的第一行为一个正整数n(0<n<=20)。第二行有n个正整数,分别表示1到n号小团体的票数。
Output
对每组测试数据,在同一个行按顺序输出1到n号小团体的权利指数。
Sample Input
2
1
10
7
5 7 4 8 6 7 5
Sample Output
1
16 22 16 24 20 22 16
#include <bits/stdc++.h>
using namespace std;
int n,t,i,j,s,tmp,a[21],ans[21],flag[21];
int main()
{
cin>>t;
while(t--)//循环t次的简便写法
{
cin>>n;
s=0;
for(i=0; i<n; i++)
{
cin>>a[i];
s+=a[i];//求总票数
}
memset(ans,0,sizeof(ans));
/*讲解一下sizeof函数,它和strlen函数类似,但是区别是strlen函数遇到“\0”之前的所有字符串长度
而sizeof是声明变量之后所占的内存数,不是实际长度*/
/*萌新可能又问了,memset是啥啊,官方解释是用来对一段内存空间全部设置为某个字符,其实多用与对定义的字符串进行初始化“ ”或者“\0”,memset方便清空一个结构类型的变量或者数组,特方便*/
for(i=0; i<(1<<n); i++)//开始枚举
{
tmp=0;
memset(flag,0,sizeof(flag));//把长度为sizeof(flag)的flag置为0,就是置为中间的数
for(j=0; j<n; j++)
{
if(i&(1<<j))//套模板
{
tmp=tmp+a[j];//m个团体的总票数
flag[j]=1;//加一个tmp就标记一下
}
}
if(tmp<=s/2)//少一个团体就当不上
{
for(j=0; j<n; j++)
{
if(tmp+a[j]>s/2&&flag[j]==0)//加上某个团体就当上了,然后当的代码就是flag没有标记的
ans[j]++;
}
}
}
for(i=0; i<=n-2; i++)
printf("%d ",ans[i]);//输出格式
printf("%d\n",ans[n-1]);
}
return 0;
}
- 此题较难理解再来个代码看看
#include <bits/stdc++.h>
using namespace std;
int a[25],b[25];//没有标记的数组
int t,n,sum;
int main()
{
while(cin>>t)
{
while(t--)
{
cin>>n;
int zong=0;
for(int i=0; i<n; i++)
{
cin>>a[i];
zong+=a[i];
}
memset(b,0,sizeof(b));
for(int i=0; i<(1<<n); i++)
{
sum=0;
for(int j=0; j<n; j++)
if(i&(1<<j))
sum+=a[j];
for(int j=0; j<n; j++)
if(i&(1<<j))
if(sum>zong/2)
if(sum-a[j]<=zong/2)
b[j]++;
}
for(int i=0; i<n; i++)
{
if(i)
printf(" %d",b[i]);
else
printf("%d",b[i]);
}
printf("\n");
}
}
return 0;
- 不知道看没看懂,反正再来个(稳住,我们能赢)
5.进阶题目趣味解题
ACM程序设计大赛是大学级别最高的脑力竞赛,素来被冠以"程序设计的奥林匹克"的尊称。大赛至今已有近40年的历史,是世界范围内历史最悠久、规模最大的程序设计竞赛。比赛形式是:从各大洲区域预赛出线的参赛队伍,于指定的时间、地点参加世界级的决赛,由1个教练、3个成员组成的小组应用一台计算机解决7到13个生活中的实际问题。
现在假设你正在参加ACM程序设计大赛,这场比赛有 n 个题目,对于第 i 个题目你有 a_i 的概率AC掉它,如果你不会呢,那么这时候队友的作用就体现出来啦,队友甲有 b_i 的概率AC掉它, 队友乙有 c_i 的概率AC掉它,那么现在教练想知道你们队伍做出 x 个题目的概率。
Input
输入一个正整数T(T<=100),表示有T组数据,对于每组数据首先输入一个 n (7<=n<=13),表示有 n 个题目,接下来输入三行,
第一行输入 n 个数a_i,第二行输入 n 个数b_i,第三行输入 n 个数c_i, 其中 a_i, b_i, c_i 的意义如题,最后输入一个 x 表示教练想要知道你们队伍做出的题目数(x>=0)。
Output
输出一行表示结果,保留4位小数
Sample Input
2
7
0.1 0.2 0.3 0.4 0.5 0.6 0.7
0.2 0.3 0.4 0.5 0.6 0.7 0.8
0.3 0.4 0.5 0.6 0.7 0.8 0.9
1
7
0.1 0.2 0.3 0.4 0.5 0.6 0.7
0.2 0.3 0.4 0.5 0.6 0.7 0.8
0.3 0.4 0.5 0.6 0.7 0.8 0.9
5
Sample Output
0.0000
0.2811
- 详解在下一节哦~~(知道你看到这已经不容易了)
- 下节提示
acm新手小白必看系列之(5)——枚举进阶例题精讲