https://codeforces.com/contest/1665/problem/D
这里有个正整数 x (1 <= x <= 1e9) 让你猜
你可以通过询问来获取信息
你可以选择两个不同的正整数 a, b (1 <= a, b <= 2e9)来进行一次询问
在一次询问后 你会获得 gcd(x + a, x + b) 的值。
定义: gcd(a, b) => a 和 b 的最大公约数
现在请你进行最多30次询问来猜出这个数
首先看题目的限制,最多猜30
次,而要猜的数是在 1 ~ 1e9
的范围,那么很自然的想到需要logN的算法。
我首先考虑的是二分答案,然而并没有什么卵用,因为没有办法从题目给定的操作中获取有用的信息。
再思考还有什么是满足logN的算法,再看题目,要猜的数最大是1e9
,询问最多30
次,让我们看下2^30
有多大: 2^30 = 1073741824 > 1e9
,所以x
的二进制表示最多30
位((2^30) - 1 = 1073741823 > 1e9
),于是又很自然地联想到二进制,顺着这个思路往下,这样每一次询问我们都应该想办法获取x
的二进制表示上的其中一位值是多少。接下来思考如何通过gcd
来获取二进制的其中一位。
对于101101
,假设我们要通过gcd
获取它的从低到高(从右到左)第3位该怎么办?既然是二进制,那就从二进制的方向去考虑。
//数字后面带 B 代表这是一个二进制表示的数
a = 101100 B = 11 * (2 ^ 2)
b = 110100 B = 13 * (2 ^ 2)
gcd(a, b) = 2 ^ 2 = 100 B
// ~~^~~第三位为1
发现什么了吗,对于上面那个例子,我们可以构造出两个数,使得它二进制表示的第3位右边全为0,而第3位的左边一位不同(这样可以保证gcd取到的数不受第3位(二进制表示下)右边的数影响),来判断它的第3位是0
还是1
。
那么如何将它右边的数字位置0呢。
考虑取1011 B 第4位的值之前进行的置零操作
//这里有 1011 B, 考虑加上某个数(别忘了题目要求)将其最高位(第4位)的右边置零
1011 B + (1000 B - 011 B) = 10000 B
1011 B
//因为1011 B最高位是第4位所以加上的是 1000 B - 前3位
//同理 如果最高位是第3位 就加上 100 B - 前2位
我们发现这样取到的第4位与原来的值相反(可以自己证明一下,一定是相反的),那么我们在处理的时候只需简单取反即可。
这样从低到高处理,就可以很方便地得出答案了。
接下来就是快乐代码时间
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
ll qry(ll a, ll b){
printf("? %lld %lld\n", a, b);
fflush(stdout);
ll x;
scanf("%lld", &x);
return x;
}
int main(){
int T;
for(scanf("%d", &T); T--;){
ll res = 0;
for(int i = 1; i <= 30; i++){
ll x = (1ll << (i - 1)) - res;
ll a = (1ll << i) + x;
ll b = x;
ll t = qry(a, b);
res |= (!(t >> (i - 1) & 1)) << (i - 1);
}
printf("! %lld\n", res);
fflush(stdout);
}
}