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;
}