CF1665 DV2 D. GCD Guess

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);
    }
}
posted @ 2022-05-31 17:02  AsindE  阅读(54)  评论(0编辑  收藏  举报