CF835E The penguin's game

CF835E The penguin's game - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

设两个 \(y\) 的下标分别是 \(a\)\(b\)

为方便说明,下文所有的第 \(i\) 位指的都是该数二进制从低到高第 \(i\) 位。

观察答案的返回值,发现返回值有四种:\(0\)\(x\)\(y\)\(x \oplus y\),而且保证这四个数是互异的。容易看出:

  • 当返回值是 \(0\) 时,说明有偶数个 \(x\) 和偶数个 \(y\)
  • 当返回值是 \(x\) 时,说明有奇数个 \(x\) 和偶数个 \(y\)
  • 当返回值是 \(y\) 时,说明有偶数个 \(x\) 和奇数个 \(y\)
  • 当返回值是 \(x \oplus y\) 时,说明有奇数个 \(x\) 和奇数个 \(y\)

显然我们更关心 \(y\) 的奇偶性。不难想出:

如果询问反馈有偶数个 \(y\),说明 \(a\)\(b\) 要么均在本次询问的集合,要么均落在外面。

如果询问反馈有奇数个 \(y\),说明 \(a\)\(b\) 有一个落在本次询问的集合,有一个落在外面。

这里我注意到,看起来后面那种反馈的信息量就更足一点。

那么本题看似选取一个子集查询的过程,其实就是将所有数分成两组的过程:查询集合里面的,和外面的。


再观察询问次数和 \(n\) 的关系。发现询问次数的上界大概率是 \(2\lceil \log_2n \rceil - 1\)。可以想到二进制或者二分。

联想到 P5304 GXOI/GZOI2019 旅行者,有一个性质是两个数的二进制一定有某一位不同。

那道题里,我们分了 \(\log_2 n\) 次组,第 \(i\) 次分组,我们将第 \(i\) 位是 \(1\) 的分到一个组,是 \(0\) 的分到一个组。

这样保证了每两个不同的数都至少一次被分在不同的组中,而且分组次数很低。


对于这个题,我们试着先询问 \(10\) 次,第 \(i\) 次询问取集合:所有第 \(i\) 位是 \(1\) 的数。

这样以来,一定有一次询问的反馈是落在了不同的组,我们设这是第 \(k\) 次询问。

而且,我们顺便获取到了 \(a \oplus b\) 的值。这意味着只要我们求出两者其中之一,就可以求出另一个。


我们知道,\(a\)\(b\) 中的第 \(k\) 位,一个是 \(1\),一个是 \(0\)。我们就令那个是 \(1\) 的是 \(a\)

考虑询问第 \(k\) 位和第 \(i\) 位都是 \(1\) 的。这样相对于查询第 \(k\) 位的那次询问,我们把第 \(k\) 位是 \(1\) 但是第 \(i\) 位是 \(0\) 的丢到集合外边了。

如果返回的结果是 \(a\)\(b\) 在同侧,说明 \(a\) 的第 \(i\) 位是 \(0\)\(a\) 被丢到外边了),否则说明 \(a\) 的第 \(i\) 位是 \(1\)

显然查询的 \(i\) 不必等于 \(k\)。所以这部分只用询问 \(9\) 次。总询问次数满足要求。


如果想不到刚刚这种策略也没关系。一个更普通的想法是,直接在第 \(k\) 位是 \(1\) 的集合中二分即可。

具体来讲,我们先查询【第 \(k\) 位是 \(1\) 的集合】的一半。如果返回的结果是 \(a\)\(b\) 同侧,说明 \(a\) 在另一半。否则说明 \(a\) 还在这一半。继续二分查找即可。

\(k\) 位是 \(1\) 的集合大小一定不超过原集合大小的一半。原因是这个集合中的任何一个数将第 \(k\) 位改成 \(0\) 后,会严格变小。显然还在原集合里面。所以第 \(k\) 位是 \(1\) 的集合大小一定不超过第 \(k\) 位是 \(0\) 的集合大小。

所以这部分可以 \(9\) 次询问达到目的。


/*
 * @Author: crab-in-the-northeast 
 * @Date: 2022-12-19 22:20:46 
 * @Last Modified by: crab-in-the-northeast
 * @Last Modified time: 2022-12-19 22:41:16
 */
#include <bits/stdc++.h>
inline int read() {
    int x = 0;
    bool f = true;
    char ch = getchar();
    for (; !isdigit(ch); ch = getchar())
        if (ch == '-')
            f = false;
    for (; isdigit(ch); ch = getchar())
        x = (x << 1) + (x << 3) + ch - '0';
    return f ? x : (~(x - 1));
}

int n, x, y;

inline int ask(int S) {
    std :: vector <int> vec;
    for (int i = 1; i <= n; ++i)
        if ((i & S) == S)
            vec.push_back(i);
    
    if (vec.empty())
        return 0;
    
    printf("? %d ", (int)vec.size());
    for (int v : vec)
        printf("%d ", v);
    puts("");
    fflush(stdout);

    int ans = read();
    return (ans == y || ans == (x ^ y)) ? 1 : 0;
}

int t[15];

int main() {
    n = read();
    x = read();
    y = read();

    int k = 0;
    int x = 0;
    
    for (int i = 0; i < 10; ++i) {
        t[i] = (1 << i);
        int ans = ask(t[i]);
        if (ans) {
            k = i;
            x |= t[i];
        }
    }

    int a = t[k];
    
    for (int i = 0; i < 10; ++i) if (i ^ k) {
        if (ask(t[i] | t[k]))
            a |= t[i];
    }
    
    int b = (a ^ x);
    if (a > b)
        a ^= b ^= a ^= b;
    
    printf("! %d %d\n", a, b);
    return 0;
}
posted @ 2022-12-19 22:56  dbxxx  阅读(25)  评论(0编辑  收藏  举报