1 条题解

  • 0
    @ 2025-10-22 23:12:35

    题意理解

    我们有一个圆形的塔,上面有 ( 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:减少询问次数

    直接枚举所有三元组询问次数太多。我们可以:

    1. 随机化选择三元组,优先选择那些包含已知边少的三元组,这样更可能发现新边。
    2. 当某个顶点已知一个邻居时,可以有针对性地找它的另一个邻居。
    3. 当大部分边已知时,可以推断剩余边。

    算法框架

    1. 初始化:每个顶点度数 0,无已知边。
    2. 重复直到所有顶点度数为 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. 从任意顶点出发,沿着已知边遍历,得到一个环,输出。

    询问次数分析

    • 每个询问最多发现 3 条边,但通常 1~2 条。
    • 总边数 = ( n )。
    • 理想情况下询问次数 ≈ ( n / 1.5 \approx 2n/3 )。
    • 实际由于随机性和推断,可以做到 ( \le n + O(1) ) 次询问。
    • 对于 ( n=500 ),这远小于 12000 的限制。

    代码实现要点

    • 用邻接矩阵或邻接表存储已知边。
    • setvector 维护未知关系的顶点对。
    • 随机选择策略避免被卡。
    • 注意刷新输出缓冲区。

    示例代码框架(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;
    }
    

    总结

    这道题的关键在于:

    1. 理解圆上三点间距离与相邻关系之间的联系。
    2. 通过询问逐步构建相邻图,直到每个顶点度数为 2。
    3. 利用随机化策略减少询问次数。
    4. 最终输出找到的哈密顿环。
    • 1

    信息

    ID
    3795
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    3
    已通过
    1
    上传者