1 条题解
-
0
题意理解
我们有一个圆形的塔,上面有 ( n ) 个门,均匀分布在圆周上(即正 ( n ) 边形的顶点)。门的编号是 ( 0 ) 到 ( n-1 ),但它们的实际排列顺序是随机的。
我们不知道它们的真实顺序,只能通过询问来推测:
- 每次询问给出三个不同的门编号 ( x, y, z )。
- 交互器会返回这三对门中 欧几里得距离最短 的所有门对(可能有 1~3 对)。
- 欧几里得距离是指圆上两点之间的直线距离(弦长)。
目标:用尽量少的询问,确定一个合法的门的环形排列顺序(顺时针或逆时针均可)。
几何性质分析
在正 ( n ) 边形中,任意三个顶点构成的三角形,其最短边对应的顶点在圆上是相邻的。
重要结论:
- 如果询问三个门 ( x, y, z ),返回的最近对是 ( (x, y) ),那么 ( x ) 和 ( y ) 在圆上是相邻的。
- 如果返回两个最近对 ( (x, y) ) 和 ( (y, z) ),那么 ( y ) 是中间那个顶点,( x ) 和 ( z ) 在 ( y ) 的两侧相邻。
- 如果返回三个最近对,说明三个顶点构成等边三角形(只可能出现在 ( n ) 是 3 的倍数且三个顶点相隔 ( n/3 ) 时)。
核心思路
我们可以通过询问来构建出相邻关系图,然后找出一个哈密顿环(即门的环形顺序)。
步骤 1:找相邻关系
对于任意两个不同的门 ( a, b ),我们可以找一个第三方门 ( c ),询问 ( (a, b, c) ):
- 如果返回的最近对包含 ( (a, b) ),则 ( a ) 和 ( b ) 相邻。
- 但这样直接做询问次数是 ( O(n^3) ),太大。
优化方法:
- 随机选择三个门 ( x, y, z ),通过一次询问可能得到 1~3 条相邻边信息。
- 用这些信息逐步构建邻接表。
步骤 2:利用已知相邻关系推理
一旦我们知道某个门 ( u ) 有两个不同的邻居 ( v, w ),那么 ( v ) 和 ( w ) 在圆上相隔两个位置(中间隔着 ( u )),它们不是相邻的。
我们可以维护一个图,边表示相邻关系,每个顶点度数 ≤ 2。当某个顶点度数为 2 时,它的两个邻居之间不能有边。
步骤 3:确定顺序
当我们得到所有顶点的度数都为 2 时,图就是若干个环。因为这是一个连通图(圆排列),所以只有一个环,就是我们要的顺序。
但要注意:可能有些相邻关系缺失,需要继续询问来补全。
步骤 4:减少询问次数
直接枚举所有三元组询问次数太多。我们可以:
- 随机化选择三元组,优先选择那些包含已知边少的三元组,这样更可能发现新边。
- 当某个顶点已知一个邻居时,可以有针对性地找它的另一个邻居。
- 当大部分边已知时,可以推断剩余边。
算法框架
- 初始化:每个顶点度数 0,无已知边。
- 重复直到所有顶点度数为 2:
- 选择一个还未确定两个邻居的顶点 ( a )。
- 选择两个不同的其他顶点 ( b, c ),使得 ( (a, b) ) 和 ( (a, c) ) 都不是已知的非相邻对。
- 询问 ( (a, b, c) )。
- 根据结果更新相邻关系:
- 如果 ( (a, b) ) 在最近对中,则添加边 ( a-b )。
- 如果 ( (a, c) ) 在最近对中,则添加边 ( a-c )。
- 如果 ( (b, c) ) 在最近对中,则添加边 ( b-c )。
- 如果某个顶点度数为 2,则标记它的两个邻居之间不能有边。
- 从任意顶点出发,沿着已知边遍历,得到一个环,输出。
询问次数分析
- 每个询问最多发现 3 条边,但通常 1~2 条。
- 总边数 = ( n )。
- 理想情况下询问次数 ≈ ( n / 1.5 \approx 2n/3 )。
- 实际由于随机性和推断,可以做到 ( \le n + O(1) ) 次询问。
- 对于 ( n=500 ),这远小于 12000 的限制。
代码实现要点
- 用邻接矩阵或邻接表存储已知边。
- 用
set或vector维护未知关系的顶点对。 - 随机选择策略避免被卡。
- 注意刷新输出缓冲区。
示例代码框架(C++)
#include <bits/stdc++.h> using namespace std; int n; vector<vector<bool>> adj; vector<set<int>> neighbors; void ask(int x, int y, int z) { cout << "? " << x << " " << y << " " << z << endl; cout.flush(); int k; cin >> k; vector<pair<int, int>> pairs; for (int i = 0; i < k; i++) { int a, b; cin >> a >> b; pairs.push_back({a, b}); } // 更新相邻关系 for (auto [a, b] : pairs) { if (!adj[a][b]) { adj[a][b] = adj[b][a] = true; neighbors[a].insert(b); neighbors[b].insert(a); // 如果某个点度数为2,则它的两个邻居不能相邻 if (neighbors[a].size() == 2) { auto it = neighbors[a].begin(); int u1 = *it; it++; int u2 = *it; // 标记u1和u2不能相邻(可选,用于推理) } if (neighbors[b].size() == 2) { auto it = neighbors[b].begin(); int u1 = *it; it++; int u2 = *it; // 标记u1和u2不能相邻 } } } } void solve() { cin >> n; adj.assign(n, vector<bool>(n, false)); neighbors.assign(n, set<int>()); // 随机化询问直到所有点度数为2 vector<int> idx(n); iota(idx.begin(), idx.end(), 0); while (true) { bool all_done = true; for (int i = 0; i < n; i++) { if (neighbors[i].size() < 2) { all_done = false; break; } } if (all_done) break; // 随机找一个度数为1或0的点作为a int a = -1; shuffle(idx.begin(), idx.end(), mt19937(time(0))); for (int i : idx) { if (neighbors[i].size() < 2) { a = i; break; } } if (a == -1) break; // 找两个不同的b,c int b = -1, c = -1; for (int i = 0; i < n; i++) { if (i != a && !adj[a][i] && neighbors[i].size() < 2) { if (b == -1) b = i; else if (c == -1) c = i; else break; } } if (b == -1 || c == -1) { // 如果找不到合适的b,c,随便选两个 for (int i = 0; i < n; i++) { if (i != a) { if (b == -1) b = i; else if (c == -1) c = i; else break; } } } ask(a, b, c); } // 构建环 vector<int> order; vector<bool> visited(n, false); int start = 0; order.push_back(start); visited[start] = true; int prev = start; int cur = *neighbors[start].begin(); while (!visited[cur]) { order.push_back(cur); visited[cur] = true; // 找下一个 for (int nb : neighbors[cur]) { if (nb != prev) { prev = cur; cur = nb; break; } } } // 输出 cout << "!"; for (int x : order) cout << " " << x; cout << endl; cout.flush(); } int main() { int t, k; cin >> t >> k; while (t--) { solve(); } return 0; }
总结
这道题的关键在于:
- 理解圆上三点间距离与相邻关系之间的联系。
- 通过询问逐步构建相邻图,直到每个顶点度数为 2。
- 利用随机化策略减少询问次数。
- 最终输出找到的哈密顿环。
- 1
信息
- ID
- 3795
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 3
- 已通过
- 1
- 上传者