Search for a command to run...
Progressive hints first, then the full explanation and implementation when you're ready to cash out.
Review status
AI-generated and still unreviewed. Double-check the details before internalizing them.
Hints
Open only as much as you need to keep the solve alive.
A query of two indices already gives a useful bit: exactly when , and otherwise it is .
First find one index containing ). Query prefixes. If the whole original sequence has answer , then there is no () anywhere, so since both bracket types exist, position must be ). Otherwise, binary search the first prefix whose answer is positive; the new character must be ).
If is a known ), then querying repeated times gives either or . It is when , and triangular when .
You can classify many positions in one query by concatenating these repeated blocks and putting one extra known ) after each block. That extra close bracket is a wall, so regular substrings cannot leak between blocks and mess up the sum.
Pick the triangular weights to be superincreasing, then decode greedily from largest to smallest. The repetition counts fit 12 unknown positions inside one query, so queries is enough.
The problem is interactive, so the solution is about designing queries. The hacked input gives the hidden string to the interactor, not to your program. Your program still only sees , then each , then the answers to its own queries.
The key is to stop thinking of as some scary bracket DP monster. We only need to manufacture query strings whose value carries encoded bits.
Tiny primitive
For a string of length :
So if we query two indices , the answer tells us whether and .
That is useful, but one bit per query is way too slow for the hard version. We need batching.
Finding one known closing bracket
We first find an index such that .
Ask the whole sequence .
If the answer is , then the original string has no non-empty regular bracket substring. Any non-empty regular bracket sequence contains some adjacent (), so the string has no () substring. Since the string contains both bracket types, all ) must come before all (. Therefore , so we can take .
Otherwise, the full sequence has answer positive. Now binary search the smallest prefix length such that
The prefix before it had no regular substring, and after appending one appears. Any newly appearing regular substring must end at position , and every non-empty regular bracket sequence ends with ). So . Take .
This costs at most
queries.
One position with a triangular number
Suppose is known to be ). For an unknown position , build this query block:
i c i c i c ... i cwith copies of .
If , the entire block is only closing brackets, so it contributes .
If , the block is
()()...()with pairs. In this string, every consecutive group of complete pairs is a regular bracket substring. There are
such substrings.
So this block contributes either or the triangular number . Nice little payload. Brackets doing binary espionage, basically.
Combining blocks safely
If we just concatenate blocks, regular substrings might span across neighboring blocks, which would corrupt the sum. That is the annoying part.
The fix is simple: after each block, append one extra known closing bracket .
For position with repetition count , use:
(i_j c) repeated m_j times, then cIf is (, this looks like copies of () followed by one extra ). If a substring starts inside this block and tries to cross that extra ), its balance eventually drops below zero, so it cannot be regular. If is ), the block is just closing brackets anyway.
Therefore every block is independent, and the query answer is exactly
Now the problem becomes: choose weights so that every subset sum is uniquely decodable.
Superincreasing weights
Use weights where every weight is larger than the sum of all previous weights. Then decoding is greedy:
( and subtract it,),The repetition counts
produce triangular weights
and these are superincreasing.
The length of one query for all 12 positions is
which is safely below the limit .
So each query classifies up to 12 positions.
Query count
) in at most queries.Total:
Yes, it fits. Barely enough to feel intentional.
Correctness proof
First, the algorithm correctly finds a closing bracket. If the whole sequence has , then it has no regular substring, hence no adjacent (). Since both bracket types exist, all closing brackets must appear before all opening brackets, so position is ). Otherwise, the algorithm binary searches the first prefix with positive . The newly created regular substring must end at that prefix's last character, and every non-empty regular bracket sequence ends with ), so that position is a closing bracket.
Next, consider one classification block for index and repetition count . If , the block contains only closing brackets and contributes . If , the block contains consecutive copies of (), whose regular substrings are exactly all consecutive groups of complete pairs, so it contributes .
The extra closing bracket after every block prevents any regular substring from crossing between blocks, because any substring that includes this separator after starting before it has negative balance at that separator. Therefore the answer to a batched query is exactly the sum of the weights corresponding to positions that are (.
The chosen weights are superincreasing, so every possible subset has a unique sum, and greedy decoding from largest to smallest recovers exactly which queried positions were (. All other queried positions are ).
Since every position is included in exactly one classification chunk, the algorithm reconstructs every character of correctly.
Complexity
The algorithm uses at most queries per test case. Each query length is at most . The local computation is tiny: aside from printing query indices.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
ll ask(const vector<int>& idx) {
cout << "? " << idx.size();
for (int x : idx) cout << ' ' << x;
cout << '\n' << flush;
ll res;
if (!(cin >> res)) exit(0);
if (res == -1) exit(0);
return res;
}
int main() {
setIO();
const vector<int> rep = {1, 2, 3, 5, 8, 12, 18, 26, 37, 53, 76, 108};
vector<ll> weight;
for (int x : rep) weight.push_back(1LL * x * (x + 1) / 2);
int tc;
cin >> tc;
while (tc--) {
int n;
cin >> n;
vector<int> all(n);
iota(all.begin(), all.end(), 1);
int close = 1;
if (ask(all) > 0) {
int l = 1, r = 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;
}
close = l;
}
string ans(n, '?');
const int B = 12;
for (int start = 1; start <= n; start += B) {
int len = min(B, n - start + 1);
vector<int> q;
for (int j = 0; j < len; j++) {
int id = start + j;
for (int k = 0; k < rep[j]; k++) {
q.push_back(id);
q.push_back(close);
}
q.push_back(close);
}
ll res = ask(q);
for (int j = len - 1; j >= 0; j--) {
if (res >= weight[j]) {
ans[start + j - 1] = '(';
res -= weight[j];
} else {
ans[start + j - 1] = ')';
}
}
}
cout << "! " << ans << '\n' << flush;
}
return 0;
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
ll ask(const vector<int>& idx) {
cout << "? " << idx.size();
for (int x : idx) cout << ' ' << x;
cout << '\n' << flush;
ll res;
if (!(cin >> res)) exit(0);
if (res == -1) exit(0);
return res;
}
int main() {
setIO();
const vector<int> rep = {1, 2, 3, 5, 8, 12, 18, 26, 37, 53, 76, 108};
vector<ll> weight;
for (int x : rep) weight.push_back(1LL * x * (x + 1) / 2);
int tc;
cin >> tc;
while (tc--) {
int n;
cin >> n;
vector<int> all(n);
iota(all.begin(), all.end(), 1);
int close = 1;
if (ask(all) > 0) {
int l = 1, r = 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;
}
close = l;
}
string ans(n, '?');
const int B = 12;
for (int start = 1; start <= n; start += B) {
int len = min(B, n - start + 1);
vector<int> q;
for (int j = 0; j < len; j++) {
int id = start + j;
for (int k = 0; k < rep[j]; k++) {
q.push_back(id);
q.push_back(close);
}
q.push_back(close);
}
ll res = ask(q);
for (int j = len - 1; j >= 0; j--) {
if (res >= weight[j]) {
ans[start + j - 1] = '(';
res -= weight[j];
} else {
ans[start + j - 1] = ')';
}
}
}
cout << "! " << ans << '\n' << flush;
}
return 0;
}