今天参加牛客暑期集训时发现了一道关于位运算的题目,题目是
- Ben_H 有一个正整数 ( x )。
- 他希望找到一个另一个正整数 ( y ),这个数必须严格小于 ( x )。
- 他希望满足方程 ( \gcd(x, y) = x \oplus y ),其中 ( \gcd ) 表示最大公约数,( \oplus ) 表示位运算中的异或操作。
- https://ac.nowcoder.com/acm/contest/81597/Ey
异或就是相同时为0,不同时为1,比如1001异或0100就是1101.
首先想到的肯定是暴力解法,但肯定会超时,看到有个异或我们就能想到这题多半与位运算有关系。
我们可以想想,假设我们输入的二进制形式的x=10001000100,不妨我们可以看看当y=10001000000时,x与y的关系,他们两个的唯一区别就是x的最后一位1在y中变为了0。
现在我们来试试z=x^y等于多少,^就是异或的意思。
x=10001000100
y=10001000000
————————
z=00000000100
很显然因为我们只改变的x中的一位数赋给y,所以他们的异或值除了改变的那个位为1之外其他位全为0。
现在我们来看看gcd(x,y)等于多少呢,用计算机算一下不难发现gcd(x,y)=6。将z转为十进制后我们发现竟然gcd(x,y)=x^y。现在我们可以得出结论当x的最后一位1变成0赋给y时满足gcd(x,y)=x^y。因为我们只改变了最后一位1,所以我们可以轻易看出十进制中y=x-lowbit(x)。lowbit(x)函数返回的就是二进制x中最后一位1转换为十进制的值。
毕竟这只是结论还缺少证明,所以现证明如下:
总所周知如果两个相邻的数,他们的最大公因数肯定就是1。比如x=7,y=6时gcd(x,y)=1。
现在我们将3与4都转化为二进制x=111,y=110。又总所周知在二进制中我们想要将一个数乘以2是通过将二进制数左移一位。现在我们将x与y都乘以2的5次方,也就是说将x与y都左移五位,便得到x=11100000,y=11000000。欸,现在是不是有点眼熟了,对的,现在的x与y的区别就是x的最后一位1变成了0赋给了y。而他们的最大公因数因双方都乘以了2的5次方,所以最大公因数1也得乘以2的5次方。而我们发现因为异或的性质x^y=二进制100000=2的5次方。也就是lowbit(x)的值。
所以得知以上性质后得出ac代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
ll lowbit(ll x){
return x&(-x);
}
void slove(){
ll x;
cin>>x;
if(x==lowbit(x)){//判断x是不是2的幂,假如x是2的幂x=100000,因为题中要求y<x的所以y只能为
cout<<"-1"<<endl;//0xxxxxx之类的,这时x^y就会使得第一位变为1,后面位无论什么情况都会
return;//使得x^y>x了。
}
cout<<x-lowbit(x)<<endl;
}
int main(){
int n;
cin>>n;
while(n--){
slove();
}
return 0;
}