非常好玩的一道题!

首先不难想到首先要找到一对括号,然后再次基础上询问其它的。简单拆分一下询问次数,$550 = 2 \times 20 + 500 + \text{eps}$,发现在找到括号后需要询问一次处理两个括号。至于为什么是 $2 \times 20$,原因便是在 $[l,r]$ 中即使有左右括号,它可能以 $\texttt{()}$ 或 $\texttt{)(}$ 的形式出现,因此需要正反各询问一次。

接下来考虑如何一次询问处理两个括号。其实方法很多,适合手玩,用上 substring 这个条件,尽量构造不对称括号序列即可。我构造的是 $\texttt{()??()}$,有以下几种情况:

设 $L,R$ 为二分找到的左/右括号位置,以第一个为例子,查询的写法为 query ({L,L,x,y,L,R}。注意,如果剩下未确定的数量是奇数,需要特殊考虑。


询问次数变为 $200$ 以内,需要一次至少测 $6$ 个。

如果接着从上一节的询问出发,直接赋值询问序列,会发现情况并不会显著增长,原因就是受到相互影响。所以进一步思考如何才能独立考虑。

尝试构造 $\texttt{(?((?((?(…}$ 的序列。具体来说,用二进制数的思想,第 $i$ 个待检测位置出现 $2^{i - 1}$ 次,即 $\texttt{(x((y((y((z((z((z((z(…}$。得到答案后,从低往高第 $i$ 位为 $1$,则说明第 $i$ 个待检测的位置为 $\texttt{)}$,否则为 $\texttt{(}$。当然,若剩余待检测位置的数量不足,直接用 $L$ 补齐即可,处理的时候特判。

最后检验一下合法性。若一次检测 $x$ 个,询问的长度为 $3(2^0 + 2^1 + \cdots + 2^{x - 1}) = 3(2^x - 1)$。由于长度不能超过 $1000$,因此 $x_{\max} = 8$,可以通过此题。


询问次数变为 $100$ 以内,需要一次至少测 $13$ 个。

接着上一节的想法,尝试构造互不影响的序列。尝试构造序列 $\texttt{(x((y(y((…}$,组内用 $\texttt{(}$ 分隔,组间用 $\texttt{((}$ 分隔。设第 $i$ 个待检测的位置出现 $dig_i$ 次,则对答案的贡献为 $sum_i = \frac{(dig_i + 1)dig_i}{2}$。进一步来说,只要满足下式条件即可:

其中 $I = \{1,2,\cdots,|sum|\}$,$J \subseteq I \setminus \{i\}$,且满足 $\forall j,k \in J, j \neq k$。

容易发现,当 $2sum_{i - 1} \le sum_i$ 时可以满足条件,通过暴力打表发现,最大的符合题目限制的 $dig$ 集合为 $\{1,2,4,6,9,13,19,28,40,57,81,115\}$。但是此时 $|dig| = 12$,按照原来的二分方式会超过限制。仔细思考可以发现原来的二分每一次的 check 需要花费 $2$ 次询问,但是我们只想要知道序列中是否存在左右括号以及括号的方向。因此,当方向未确定时,我们将询问的序列正反拼接,得到有无左右括号的信息。一旦在某次询问中得知存在左右括号,直接花费额外的一次询问定方向。在得知方向后,每次二分只需要一次询问去缩小范围。二分的询问次数的上确界为 $\lceil{\log (10^3)} + 1 \rceil = 11$ 次,总询问次数的上确界为 $11 + \lceil \frac{10^3}{12} \rceil = 11 + 84 = 95$ 次,同时单次询问长度不会超过 $1000$,已经可以通过此题。

但能否进一步优化呢?按照之前的互不影响的条件去构造基,可以尝试写一个 $O(k^2 2^k)$ 的状压构造并手动调整,在此不过多赘述,直接给出一组长度为 $13$ 的可行构造:$\{1,2,4,5,8,11,16,23,33,57,74,105,150\}$,可以证明不存在比 $13$ 更大且满足条件限制的基。此时总询问次数的上确界可以降为 $11 + \lceil \frac{10^3}{13} \rceil = 11 + 77 = 88$ 次,理论上应该已经达到最优解了。


完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#include <bits/stdc++.h>
#define init(x) memset (x,0,sizeof (x))
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
#define pii pair <int,int>
using namespace std;
const int MAX = 1e5 + 5;
const int MOD = 1e9 + 7;
const int UP = 13;
inline int read ();
int dig[] = {1,2,4,5,8,11,16,23,33,57,74,105,150};
int query (vector <int> lst)
{
printf ("? %d ",(int)lst.size ());
for (auto v : lst) printf ("%d ",v);
puts ("");fflush (stdout);
return read ();
}
void solve ()
{
int n = read ();
int l = 1,r = n,L = -1,R = -1,sure = -1;
vector <char> ans (n + 1);
auto check = [&] (int l,int r) -> bool
{
vector <int> lst;
for (int i = l;i <= r;++i) lst.push_back (i);
if (sure == 0) return query (lst);
else if (sure == 1)
{
reverse (lst.begin (),lst.end ());
return query (lst);
}
for (int i = r;i >= l;--i) lst.push_back (i);
if (!query (lst)) return false;
for (int i = l;i <= r;++i) lst.pop_back ();
if (query (lst)) sure = 0;
else sure = 1;
return true;
};
while (1)
{
if (l + 1 == r)
{
if (sure == -1)
{
if (query ({l,r})) L = l,R = r;
else L = r,R = l;
}
else if (sure == 0) L = l,R = r;
else L = r,R = l;
break;
}
int mid = (l + r) >> 1;
if (check (l,mid)) r = mid;
else l = mid;
}
ans[L] = '(';ans[R] = ')';
vector <int> p;
for (int i = 1;i <= n;++i)
if (i != L && i != R) p.push_back (i);
int pos = 0,sz = p.size ();
while (pos < sz)
{
vector <int> nw (UP,L);
for (int i = 0;i < UP;++i)
if (pos + i < sz) nw[i] = p[pos + i];
vector <int> lst;
for (int i = 0;i < UP;++i)
{
for (int j = 0;j < dig[i];++j) lst.push_back (L),lst.push_back (nw[i]);
lst.push_back (L);
}
int res = query (lst);
for (int i = UP - 1;~i;--i)
{
if (nw[i] == L) continue;
if (res >= (1 + dig[i]) * dig[i] / 2) ans[nw[i]] = ')',res -= (1 + dig[i]) * dig[i] / 2;
else ans[nw[i]] = '(';
}
pos += UP;
}
printf ("! ");
for (int i = 1;i <= n;++i) printf ("%c",ans[i]);
puts ("");fflush (stdout);
}
int main ()
{
int t = read ();
while (t--) solve ();
return 0;
}
inline int read ()
{
int s = 0;int f = 1;
char ch = getchar ();
while ((ch < '0' || ch > '9') && ch != EOF)
{
if (ch == '-') f = -1;
ch = getchar ();
}
while (ch >= '0' && ch <= '9')
{
s = s * 10 + ch - '0';
ch = getchar ();
}
return s * f;
}