Codeforces 1364E X-OR
Description
这是一道交互题。
有一个 $0 \sim n-1$ 的排列 $p$,下标为 $1 \sim n$,每次询问 ? a b (要求 $a \ne b$)返回 $p_a \lor p_b$ 的值。在不超过 $4269$ 次询问后回答排列 $p$,回答方式为 ! p1 p2 ... pn。
$3 \le n \le 2048$
Solution
下面规范书写使用 $\lor$ 来表示位或,$\operatorname{Query}(x, y)$ 表示题目中的询问 ? x y。
首先,可以发现 $0 \lor x = x$,这意味着,只要把排列里面的 $0$ 的位置找出来了,就可以把它分别和其他位置询问,来得到这个排列。
几个结论:
因为 $y \lor x \ge x$,所以不存在其它的数 $\lor x$ 比 $0 \lor x$ 小。换句话说,如果有 $a \lor x < b \lor x$,则 $b$ 必然不是 $0$(下面简称「结论 1」)。
因为 $0 \lor x = x$,如果 $a \ne b$,则必然有 $a \lor 0 \ne b \lor 0$。也就是说,如果存在一个数 $c$,使得 $a \ne b, a \lor c = b \lor c$,则 $c$ 不可能是 $0$。(下面简称「结论 2」)。
问题的关键出在了怎么找到 $0$ 所在的位置。
首先,我们可以把 $1 \sim n$ 打乱顺序(开始信仰了),记作 $p$;存答案的排列记作 $ans$(也就是题目中的 $p$)。$p$ 的下标是 $0 \sim n - 1$,$ans$ 的下标是 $1 \sim n$。
比方说,我们有两个 $0$ 位置的候选选项 $a$ 和 $b$($a, b \in [1, n]$),且它们的或和是已知的(即已知 $val = \operatorname{Query}(a, b)$),现在我们枚举到了 $p_i = c$ 这个位置,我们发起询问 $tmp = \operatorname{Query}(a, c)$,尝试用 $c$ 去更新 $a$ 和 $b$:
1. 如果 $tmp < val$,也就是 $p_c \lor p_a < p_b \lor p_a$,根据结论 1,可知 $b$ 所在的位置不可能是 $0$,所以 $b \gets c, val \gets tmp$。
2. 如果 $tmp > val$,也就是 $p_c \lor p_a > p_b \lor p_a$,根据结论 1,可知 $c$ 所在的位置不可能是 $0$,故不用更新。
3. 如果 $tmp = val$,也就是 $p_c \lor p_a = p_b \lor p_a$,因为 $p_c$ 肯定是不等于 $p_b$ 的,根据结论 2,可知 $a$ 所在的位置不可能是 $0$,所以 $a \gets c, val \gets \operatorname{Query(b, c)}$。
全部更新完后,我们就得到了两个 $0$ 的候选项 $a$ 和 $b$,我们开始随机选择 $c \in [1, n]$,在保证 $a \ne c, b \ne c$ 的前提下,每次询问 $t_1 = \operatorname{Query}(a, c)$ 和 $t_2 = \operatorname{Query}(b, c)$,如果 $t_1 \ne t_2$,根据结论 1 就可以舍去其中一个(留下结果较小的那个),即确定了 $0$ 的位置。
最后就可以快乐地和每个位置询问得出答案啦!
下面分析询问次数:
最终求答案肯定是 $n$ 级别次询问,第一步也肯定对于每个位置至少做了一次询问,所以总共做了 $2n$ 级别次询问。现在不能确定次数的就是第一步的情况 3 和第二步,通过大量尝试可知,询问次数非常少,最坏情况下仅仅十几而已,感性上可以理解为恰好选取一个数 $y$ 包含了 $x$ 的所有位的概率显然是十分小的,所以在随机情况下不会超过限制次数。有严谨证明可以联系我。
#include <bits/stdc++.h> using namespace std; const int N = (1 << 11) + 5; int n, p[N], ans[N]; inline int query(int x, int y) { cout << "? " << x << ' ' << y << endl; cout.flush(); int res; cin >> res; return res; } int main() { srand(20050910); cin >> n; for(int i = 0; i < n; i++) p[i] = i + 1; random_shuffle(p, p + n); int a = p[0], b = p[1], val = query(p[0], p[1]); for(int i = 2; i < n; i++) { int tmp = query(b, p[i]); if(tmp < val) { a = p[i]; val = tmp; } else if(tmp == val) { b = p[i]; val = query(a, p[i]); } } int id0; while(1) { int i = rand() % n + 1; if(i == a || i == b) continue; int t1 = query(i, a), t2 = query(i, b); if(t1 == t2) continue; id0 = t1 < t2 ? a : b; break; } for(int i = 1; i <= n; i++) if(i != id0) ans[i] = query(i, id0); cout << "! "; for(int i = 1; i <= n; i++) cout << ans[i] << ' '; cout << endl; cout.flush(); return 0; }