大一寒假集训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。
所以要对程序进行优化,可以先读入一个人的名字,之后的2n-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;
}
找出哪个数只出现了一次
和上一题一样的思想,自定义一个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;
}
初学二进制枚举觉得很不好理解,其实后来发现他有点规律可循,
开始说的,一般只用于有两种情况的,数据较少的。
然后用的具体格式也大致一样。
两层循环 一个控制移位 ,一个负责控制条件
暴力遍历一遍就能得出最后结果。