Description
这是一道交互题。
有一个 $0 sim n-1$ 的排列 $p$,下标为 $1 sim n$,每次询问 ? a b (要求 $a e 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 e b$,则必然有 $a lor 0 e b lor 0$。也就是说,如果存在一个数 $c$,使得 $a e 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 e c, b e c$ 的前提下,每次询问 $t_1 = operatorname{Query}(a, c)$ 和 $t_2 = operatorname{Query}(b, c)$,如果 $t_1 e 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; }