Codeforces 1364E X-OR

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;
}
原文地址:https://www.cnblogs.com/syksykCCC/p/CF1364E.html