1 条题解

  • 0
    @ 2026-5-16 16:12:26

    以下是根据标程思路整理的详细题解。


    题意分析

    有一个长度为 nn 的隐藏括号序列 ss,仅由 '('')' 组成,且至少包含一对括号。
    交互器提供一个函数 f(t)f(t),表示字符串 tt非空正则括号子串的数量(子串连续)。正则括号序列即通常的合法括号序列。
    我们可以在 550550 次询问内确定 ss,每次询问可以指定任意 k1000k \le 1000 个位置(可重复),得到这些字符顺次拼接后的 ff 值。


    核心观察

    性质1:若序列中不存在 "()" 子串,则 f(s)=0f(s)=0。此时所有 ')' 必定在全部 '(' 之前,即序列形如 ))...)((...(
    由于 ss 至少含有一对括号,此时必有 s1=’)’,  sn=’(’s_1 = \texttt{')'},\; s_n = \texttt{'('}

    性质2:若 f(s)>0f(s) > 0,则必然存在相邻的 "()"。通过二分查找可使我们找到这样一对位置:
    g(i)=f(s1s2si)g(i) = f(s_1 s_2 \dots s_i)。因为一旦前缀包含了第一个 "()"g(i)g(i) 就会 >0>0;而在此之前均为 00
    因此最小的 ii 满足 g(i)>0g(i)>0 给出 si1=’(’,si=’)’s_{i-1}=\texttt{'('}, s_i=\texttt{')'}。二分需约 log2n\lceil \log_2 n \rceil 次查询。

    这样,我们获得了一个已知的 '(' 位置 xx 和一个已知的 ')' 位置 yy


    高效确定剩余字符

    对于任意两个待确定的字符 su,svs_u, s_v,我们可以利用已知的 x,yx,y 构造一个长度为 66 的查询字符串:

    $$t = s_u \;+\; \texttt{')'} \;+\; s_v \;+\; \texttt{')'} \;+\; \texttt{'('} \;+\; \texttt{')'} $$

    其中第一个字符取位置 uu,第二个字符取位置 yy(已知 ')'),第三个取位置 vv,第四个再取位置 yy,第五个取位置 xx(已知 '('),第六个再取位置 yy
    计算该字符串的 f(t)f(t),其值可唯一确定 (su,sv)(s_u, s_v) 的组合:

    sus_u svs_v 字符串 tt 的形态 正则子串分析 f(t)f(t)
    '(' '(' ()()() 三个 "()"、两个 "()()"、一个整体,共 66 66
    ')' ()))() 仅位置 [1,2][1,2][5,6][5,6]"()",共 22 22
    ')' '(' ))()() 位置 [3,4],[5,6][3,4],[5,6]"()" 以及 [3,6][3,6]"()()",共 33 33
    ')' ))))() 仅位置 [5,6][5,6]"()",共 11 11

    这样,一次查询即可确定两个未知字符,每个字符的开销仅为 0.50.5 次查询。


    完整算法流程

    1. 询问整个序列 f(s1sn)f(s_1\dots s_n)

      • 若结果为 00:直接得到 s1=’)’,  sn=’(’s_1=\texttt{')'},\; s_n=\texttt{'('}
      • 若结果 >0>0:二分查找最小的 ii 使 f(s1si)>0f(s_1\dots s_i) > 0,从而得到 x=i1x=i-1'('),y=iy=i')')。
    2. 将剩余未确定的位置两两配对,对每一对用上述 66 字符查询确定两个字符。

    3. 若最后剩余一个字符单独无法配对,可用类似方法单独查询(例如构造包含已知括号与待定字符的短串),或利用已确定的相邻字符与已知括号构造查询,依然可以 11 次确定。

    4. 输出确定的完整序列 ! s


    查询次数分析

    • 判断全 00 或二分找 "()"O(logn)10O(\log n) \le 10 次。
    • 两两确定:(n2)/2499\lceil (n-2)/2 \rceil \le 499 次(n1000n\le 1000)。
    • 可能剩余的单个字符:1\le 1 次。

    总查询次数 10+499+1=510<550\le 10 + 499 + 1 = 510 < 550,满足要求。


    正确性证明

    上述每一步均基于 f(t)f(t) 的严格数学定义,构造的 tt 仅依赖于已知的 '('')' 位置,且四种组合的 ff 值互不相同,故可唯一确定。整个交互过程是非自适应的,序列固定,所以策略正确。


    #include <bits/stdc++.h>
    using namespace std;
    
    int ask(const vector<int>& idx) {
        cout << "? " << idx.size();
        for (int x : idx) cout << ' ' << x;
        cout << endl;
        int res;
        cin >> res;
        return res;
    }
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        int t;
        cin >> t;
        while (t--) {
            int n;
            cin >> n;
            vector<char> s(n + 1);
            // 查询整个序列
            vector<int> all(n);
            iota(all.begin(), all.end(), 1);
            int total = ask(all);
            int x = -1, y = -1;
            if (total == 0) {
                s[1] = ')';
                s[n] = '(';
                x = n; // 已知 '('
                y = 1; // 已知 ')'
            } else {
                // 二分找第一个使 f(1..i) > 0 的位置 i
                int l = 2, r = n, i = n;
                while (l < r) {
                    int mid = (l + r) / 2;
                    vector<int> pref(mid);
                    iota(pref.begin(), pref.end(), 1);
                    if (ask(pref) > 0) r = mid;
                    else l = mid + 1;
                }
                i = l;
                x = i - 1;
                y = i;
                s[x] = '(';
                s[y] = ')';
            }
            // 剩余未知位置
            vector<int> unk;
            for (int i = 1; i <= n; ++i) {
                if (i != x && i != y) unk.push_back(i);
            }
            // 两两确定
            for (size_t j = 0; j + 1 < unk.size(); j += 2) {
                int u = unk[j], v = unk[j + 1];
                int res = ask({u, y, v, y, x, y});
                if (res == 6)      { s[u] = '('; s[v] = '('; }
                else if (res == 2) { s[u] = '('; s[v] = ')'; }
                else if (res == 3) { s[u] = ')'; s[v] = '('; }
                else               { s[u] = ')'; s[v] = ')'; }
            }
            // 处理单个剩余
            if (unk.size() % 2 == 1) {
                int u = unk.back();
                int res = ask({u, y, x});
                s[u] = (res == 1 ? '(' : ')');
            }
            // 输出答案
            cout << "! ";
            for (int i = 1; i <= n; ++i) cout << s[i];
            cout << endl;
        }
        return 0;
    }
    

    总结

    本题利用正则括号子串计数函数的特性,首先定位一对关键括号,然后设计巧妙的询问模板,实现了每次查询同时推断两个字符,从而在极少的交互次数内完整复原序列。

    • 1

    信息

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