• 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;
    }
  • 相关阅读:
    迷你版jQuery——zepto核心源码分析
    zepto.js 源码解析
    zepto.js swipe实现触屏tab菜单
    zepto.js 处理Touch事件
    Zepto 使用中的一些注意点(转)
    判断js对象的数据类型,有没有一个最完美的方法?
    html 5 本地数据库(Web Sql Database)核心方法openDatabase、transaction、executeSql 详解
    HTML5本地存储——Web SQL Database
    js事件监听器用法实例详解-注册与注销监听封装
    10 个非常有用的 AngularJS 框架
  • 原文地址:https://www.cnblogs.com/syksykCCC/p/CF1364E.html
Copyright © 2020-2023  润新知