1 条题解
-
0
以下是根据标程思路整理的详细题解。
题意分析
有一个长度为 的隐藏括号序列 ,仅由
'('和')'组成,且至少包含一对括号。
交互器提供一个函数 ,表示字符串 中非空正则括号子串的数量(子串连续)。正则括号序列即通常的合法括号序列。
我们可以在 次询问内确定 ,每次询问可以指定任意 个位置(可重复),得到这些字符顺次拼接后的 值。
核心观察
性质1:若序列中不存在
"()"子串,则 。此时所有')'必定在全部'('之前,即序列形如))...)((...(。
由于 至少含有一对括号,此时必有 。性质2:若 ,则必然存在相邻的
"()"。通过二分查找可使我们找到这样一对位置:
令 。因为一旦前缀包含了第一个"()", 就会 ;而在此之前均为 。
因此最小的 满足 给出 。二分需约 次查询。这样,我们获得了一个已知的
'('位置 和一个已知的')'位置 。
高效确定剩余字符
对于任意两个待确定的字符 ,我们可以利用已知的 构造一个长度为 的查询字符串:
$$t = s_u \;+\; \texttt{')'} \;+\; s_v \;+\; \texttt{')'} \;+\; \texttt{'('} \;+\; \texttt{')'} $$其中第一个字符取位置 ,第二个字符取位置 (已知
')'),第三个取位置 ,第四个再取位置 ,第五个取位置 (已知'('),第六个再取位置 。
计算该字符串的 ,其值可唯一确定 的组合:字符串 的形态 正则子串分析 '(''('()()()三个 "()"、两个"()()"、一个整体,共 个')'()))()仅位置 和 的 "()",共 个')''('))()()位置 的 "()"以及 的"()()",共 个')'))))()仅位置 的 "()",共 个这样,一次查询即可确定两个未知字符,每个字符的开销仅为 次查询。
完整算法流程
-
询问整个序列 。
- 若结果为 :直接得到 。
- 若结果 :二分查找最小的 使 ,从而得到 (
'('),(')')。
-
将剩余未确定的位置两两配对,对每一对用上述 字符查询确定两个字符。
-
若最后剩余一个字符单独无法配对,可用类似方法单独查询(例如构造包含已知括号与待定字符的短串),或利用已确定的相邻字符与已知括号构造查询,依然可以 次确定。
-
输出确定的完整序列
! s。
查询次数分析
- 判断全 或二分找
"()": 次。 - 两两确定: 次()。
- 可能剩余的单个字符: 次。
总查询次数 ,满足要求。
正确性证明
上述每一步均基于 的严格数学定义,构造的 仅依赖于已知的
'('和')'位置,且四种组合的 值互不相同,故可唯一确定。整个交互过程是非自适应的,序列固定,所以策略正确。
#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
- 上传者