大一寒假培训(四)
今天学习的知识是二进制枚举
首先,了解一下二进制操作
二进制操作
算数位运算(与、或、非、异或)
与(&)
对于指定的两个数 A=60(0011 1100)
B=13(0000 1101)
执行一下操作 A&B=12(0000 1100)
就是对二进制每一位进行了一次与操作,同为1,结果为1,否则为0
或(|)
对于指定的两个数A=60(0011 1100)
B=13(0000 1101)
执行一下操作 A|B=61(0011 1101)
就是对二进制每一位进行了一次或操作,同为0,结果为0,否则为1
非(~)
对于指定的一个数 A=60(0011 1100)
执行以下操作 ~A=195(1100 0011)
就是对二进制每一位进行了一次取反操作,若二进制数位0,则变成1,否则变成0
此时是只考虑了一个字节(8 bite)
异或
异或,英文为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)
就是对二进制每一位进行了一次异或操作,即非进位加法。
二进制移位操作符
移位操作有两种左移与右移:
1、左移<<
例如:A=5(0101)
如果向左移动一位即A<<1结果为1010,十进制的10。
二进制中的左移就是乘二操作,在c/c++中左移运算速度比乘二速度要快。
2、右移>>
例如:A=5(0101)
如果向右移动一位即A>>1结果为0010,十进制的2。
二进制中的左移就是除二操作(舍去小数)。
二进制枚举
二进制枚举利用的是二进制下n位长度的数有2n个,一个有n个元素的集合子集个数也为2n个
所以可以利用二进制的1,0和集合中的元素联系起来
他可以实现组合也可以实现容斥
对一个二进制来说1代表取这个元素0代表不取这个元素,1和0所在的位置代表元素的位置
这样的思想在有时候给题目有了很大的方便
举个例子
如集合{a,b,c,d,e}当二进制00000就代表什么都不取, 10000代表取a,01000代表取b,11000代表取a,b
如此所以我们需要枚举的数量就是00000到11111,也就是0到1<<n位,<<代表左移操作
nefu 1172 Find different
题意:输入奇数个数,其中只有一个数不是两两配对的,请把它找出来(为什么要把这个单身狗找出来呢)
这道题的原理上面讲过了:对于一个值异或同一个值两次,则结果还是原值
所以这道题只需要把所输入的数据一直异或,单身狗自动就跑出来了
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n,ans,x;
while(cin>>n){
ans=0;
for(int i=0;i<n;i++){
cin>>x;
ans=ans^x; //异或同一个值两次,等于没有变化
}
cout << ans << endl;
}
return 0;
}
nefu 1205 和为K
Description
给出长度为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 <bits/stdc++.h>
using namespace std;
int num[25];
int main()
{
int n,k,ans,flag;
while(cin>>n>>k){
flag=1;
for(int i=0;i<n;i++)
cin >> num[i];
for(int i=0;i<(1<<n);i++){ //已知有n个数,故有2^n^种情况,这里把这些情况遍历一遍
ans=0;
for(int j=0;j<n;j++) //j的作用是判断i在二进制种的每一位上是0还是1
if(i&(1<<j)) //1<<j在二进制下为1后面j个0(从后往前第j+1位为1),条件即为i从后往前的第j+1位与1进行与运算且结果不为0
ans+=num[j]; //若i的那一位为0,则结果为0,否则结果不为0
if(ans==k){
cout << "Yes" << endl;
flag=0;
break;
}
}
if(flag==1)
cout << "No" << endl;
}
return 0;
}
nefu 1505 陈老师加油
Description
陈老师经常开车在哈尔滨的大街上行走,假设刚开始油箱里有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,lu,jia,dai,ans;
while(cin>>t){
ans=0;
for(int i=0;i<(1<<15);i++){
lu=jia=0;dai=t;
for(int j=0;j<15;j++){ //0为加油站,1为路口
if(i&(1<<j)){ //如果不为0,则i的那一位为1,即路口
dai--;
lu++;
}
else{ //此时为0,即加油站
dai*=2;
jia++;
}
if(dai<=0) //如果油没了,就直接退出循环
break;
}
if(lu==10&&jia==5&&dai==0) //保证最后的油量为0
ans++;
}
cout << ans << endl;
}
return 0;
}
nefu 1518 纸牌游戏
Description
给你一些扑克,每张都对应一个点数,分别对应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
跟和为K那道题很像,只是这次是要求方案数
#include <bits/stdc++.h>
using namespace std;
int a[25];
int main()
{
int n,p,sum,ans;
while(cin>>n>>p){
for(int i=0;i<n;i++)
cin>>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) //总和是p时,将方案数加1
ans++;
}
cout << ans << endl;
}
return 0;
}
nefu 1641 权利指数
Description
在选举问题中,总共有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 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))&&sum>zong/2&&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;
}
nefu 1285 趣味解题
Description
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
#include <bits/stdc++.h>
using namespace std;
double a[15],b[15],c[15],ac[15],wa[15];
int main()
{
ios::sync_with_stdio(0);
int t,n,x,ans;
double gai,sum;
while(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;
for(int i=0;i<n;i++){
wa[i]=(1-a[i])*(1-b[i])*(1-c[i]); //都做不出来的时候算没做出来
ac[i]=1-wa[i];
}
sum=0;
for(int i=0;i<(1<<n);i++){
gai=1;ans=0;
for(int j=0;j<n;j++){
if(i&(1<<j)){ //做出来的
gai=gai*ac[j];
ans++;
} //没做出来的
else
gai=gai*wa[j];
}
if(ans==x)
sum=sum+gai;
}
printf("%.4lf\n",sum);
}
}
return 0;
}