1 条题解
-
0
题解
这是一道交互题,要求通过若干次查询找出一个隐藏的排列 (满足 )。查询方式为:给出一个排列 ,评测机返回满足 、 且 的二元组 的数量。其中 是程序一开始选择的固定常数。
核心思路
我们希望对每个位置 ,找出谁映射到它,即找到 使得 。
选择 :取 ,令 。
数组划分:根据 ,将下标区间划分为三段:
$$A = [1,\; x], \quad B = [x+2,\; n-x], \quad C = [n-x+1,\; n] $$注意位置 本身是单独的特殊位置,不属于任何一段。
一次二分查询
假设我们要找 的那个 。
构造查询排列 :
- 令 (将 放在特殊位置 )。
- 将候选 集合的一半填入 和 区域。
- 将候选集的另一半填入 区域。
做两次查询:
- 用构造好的 查询一次,得到答案 。
- 将 中除位置 外的所有元素反转,再查询一次,得到答案 。
为何 能区分 在哪一半?
考虑排列 的边 组成的图(每个点出度为 ,入度为 ,形成若干环)。
对于目标 ,设 ,。
- 边 (入边):若 在 中,则它只会在某一次查询中被计数(因为反转会改变其位置是否在 )。若 在 中,则两次查询都不会计数(因为 中的下标与 的距离使得不满足计数条件)。
- 边 (出边):由于 固定在位置 ,这条边永远不会被计数。
- 其他所有边:每次查询都会贡献恰好 。
因此:
- 若 :(所有边 条,减去出边 条,入边只计一次)。
- 若 :(所有边,减去出边 条,入边完全不计)。
通过比较 的值,就能确定 在哪一半!
二分查找
重复上述过程:
- 将当前候选集分成两半。
- 一半放入 ,另一半放入 。
- 用两次查询(正排 + 反转)判断 落在哪一半。
- 缩小候选集,继续递归。
每次迭代候选集大小减半,找到一个 需要约 次查询。找出全部 个映射关系需要约 次查询。当 时,,满足查询次数限制。
算法步骤
- 选择 。
- 对每个未知的 ( 到 ):
- 初始化候选集为所有下标(除 本身)。
- 当候选集大小 :
- 将候选集均分,一半放入 ,另一半放入 。
- 令 ,构造 。
- 查询 得 ,反转 (除 )查询得 。
- 若 ,则 在前一半;否则在后一半。
- 更新候选集。
- 确定唯一的 ,记录 。
- 输出完整排列。
查询次数:,在 时远小于 。
代码参考
#include <bits/stdc++.h> using namespace std; int n, k; int query(vector<int> &q) { cout << "? "; for (int i = 1; i <= n; i++) cout << q[i] << " \n"[i == n]; cout.flush(); int res; cin >> res; return res; } void solve() { cin >> n; k = (n + 1) / 4 + 1; cout << k << "\n"; cout.flush(); vector<int> p(n + 1, 0); for (int target = 1; target <= n; target++) { vector<int> cand; for (int j = 1; j <= n; j++) { if (j != target) cand.push_back(j); } while (cand.size() > 1) { int sz = cand.size(); int half = sz / 2; vector<int> q(n + 1); int idx = 0; // 前半放入 A∪C,后半放入 B int ptra = 1, ptrc = n; for (int t = 0; t < half; t++) { int val = cand[idx++]; if (ptra <= k - 1) q[ptra++] = val; else q[ptrc--] = val; } for (int t = half; t < sz; t++) { int val = cand[idx++]; q[k + 1 + t - half] = val; // 填充 B 区 } q[k] = target; int a = query(q); // 反转 q(除 k) reverse(q.begin() + 1, q.begin() + k); reverse(q.begin() + k + 1, q.end()); int b = query(q); vector<int> new_cand; if (a + b == n - 2) { for (int t = 0; t < half; t++) new_cand.push_back(cand[t]); } else { for (int t = half; t < sz; t++) new_cand.push_back(cand[t]); } cand = new_cand; } p[cand[0]] = target; } cout << "! "; for (int i = 1; i <= n; i++) cout << p[i] << " \n"[i == n]; cout.flush(); } int main() { ios::sync_with_stdio(false); cin.tie(0); int t; cin >> t; while (t--) solve(); return 0; }注:上述实现为展示核心二分查找逻辑的简化版,实际下标填充需根据 三段的大小精确控制。完整实现可见官方题解。
- 1
信息
- ID
- 6958
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 6
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者