1 条题解

  • 0
    @ 2026-5-18 17:01:18

    E. Broken Queries 题解

    题目回顾

    这是一道交互题,核心规则:

    1. 隐藏一个长度为 nnnn22 的幂)的二进制数组,恰好有一个 11,其余为 00
    2. 隐藏整数 k(2kn1)k(2\le k\le n-1),查询区间 [l,r][l,r] 时:
      • 区间长度 <k<k:返回真实区间和11 表示 11 在区间内,00 反之);
      • 区间长度 k\ge k:返回取反后的结果
    3. 最多使用 3333 次查询,求出 kk

    核心观察

    1. 数组仅有一个 11,因此区间查询结果仅由两个因素决定11 的位置 pp、区间长度是否 k\ge k
    2. nn22 的幂,天然适合二分查找(查询次数 log2(230)=3033\log_2(2^{30})=30 \le33,完全满足限制);
    3. 我们可以通过分块查询,快速定位 11 的大致位置,再二分精确求出 kk

    解题思路

    代码的核心逻辑分为 4 个步骤,全程利用 nn22 的幂的特性:

    1. 四等分定位:将数组分为 4 个等大的块,查询前两个块,判断 11左半部分还是右半部分
    2. 划分 kk 范围:查询长度为 n/2n/2 的区间,判断 kk小值n/2\le n/2)还是大值>n/2>n/2);
    3. 二分精确求解:根据 kk 的范围,二分查找最小的 kk(即查询结果发生翻转的临界长度);
    4. 输出答案:二分得到的结果即为隐藏的 kk

    代码详解

    原代码问题修复

    原代码存在交互题致命bug,已在完整AC代码中修复:

    1. 缺少输出刷新 flush(交互题必须每次查询后刷新缓冲区);
    2. n230n\le2^{30}int 溢出,改用 long long
    3. 未处理返回值 1-1(题目要求读到 1-1 立即退出);
    4. 变量名冲突、逻辑细节优化。

    函数与变量解释

    1. qry 函数:封装查询逻辑,支持区间翻转(适配 11 在右半部分的情况);
    2. firstHalf:标记 11 在数组左半部分true)还是右半部分(false);
    3. kSmall:标记 kk小值n/2\le n/2)还是大值(>n/2>n/2);
    4. 二分查找:倍增式二分,快速缩小 kk 的范围。

    完整AC代码

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    // 交互查询函数:l,r为0下标,rev=翻转标记,n=数组长度
    ll qry(ll l, ll r, bool rev, ll n) {
        if (rev) {
            // 区间翻转:适配1在右半部分的情况
            ll t = n - l;
            l = n - r;
            r = t;
        }
        // 输出查询,转换为题目要求的1下标
        cout << "? " << l + 1 << " " << r << endl;
        // 强制刷新输出缓冲区(交互题必备)
        cout.flush();
        ll res;
        cin >> res;
        // 读到-1,程序立即退出(题目强制要求)
        if (res == -1) exit(0);
        return res;
    }
    
    void solve() {
        ll n;
        cin >> n;
        // 步骤1:查询前两个四等分块,判断1在左半/右半
        ll a = qry(0, n / 4, false, n);
        ll b = qry(n / 4, n / 2, false, n);
        bool firstHalf = true;
        if (a == b) firstHalf = false; // 1在右半部分
    
        // 步骤2:判断k是小值(<=n/2)还是大值(>n/2)
        bool kSmall = true;
        if (qry(0, n / 2, firstHalf, n) == 0) {
            kSmall = false;
        }
    
        ll bs = 0;
        // 步骤3:二分查找精确的k值
        if (kSmall) {
            // 情况1:k <= n/2,正向二分
            for (ll k = n / 4; k > 0; k /= 2) {
                if (qry(0, bs + k, firstHalf, n) == 0) {
                    bs += k;
                }
            }
        } else {
            // 情况2:k > n/2,反向二分
            bs = n / 2 - 1;
            for (ll k = n / 4; k > 0; k /= 2) {
                if (qry(0, bs + k, !firstHalf, n) == 1) {
                    bs += k;
                }
            }
        }
    
        // 输出答案
        cout << "! " << bs + 1 << endl;
        cout.flush();
    }
    
    int main() {
        // 关闭同步,加速输入输出
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        int T;
        cin >> T;
        while (T--) solve();
        return 0;
    }
    

    复杂度分析

    1. 时间复杂度O(Tlogn)\mathcal{O}(T\log n)
      • 每组测试用例仅执行 log2n\log_2 n 次查询(最多 3030 次),远小于题目限制的 3333 次;
      • 测试用例数 T500T\le500,总查询数 500×30=15000500\times30=15000,完全合规。
    2. 空间复杂度O(1)\mathcal{O}(1),仅使用常数变量。

    正确性证明

    1. 定位 11 的位置:四等分查询可以无歧义判断 11 在左/右半部分;
    2. 划分 kk 范围:长度为 n/2n/2 的查询,能直接区分 kn/2k\le n/2k>n/2k>n/2
    3. 二分求解:利用查询结果的翻转特性,二分可以精准找到临界长度 kk
    4. 查询次数log2(230)=3033\log_2(2^{30})=30 \le33,满足题目硬限制。

    关键注意事项(交互题必看)

    1. 每次输出后必须刷新缓冲区cout.flush()),否则会触发超时;
    2. 必须处理返回值 1-1,读到后立即退出程序;
    3. 数据类型用 long longnn 最大为 2302^{30}int 会溢出;
    4. 题目数组下标为 11,代码内部用 00 下标更方便,查询时转换即可。
    • 1

    信息

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