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.
Stop caring about . Query only two values, e.g. and . Then the answer, minus , is Boolean, and
View the function as an ordered tree. For any subtree interval , its root is either or . Compare the earliest prefix that can make the Boolean answer with the latest suffix that can make it .
If the top operation is where means and means , then is neutral. You want to find a cut so the current function is
Careful: the first prefix producing may stop inside the first child, because nested operations can already be satisfied. Take a second disjoint witness, set everything before it to , then greedily delete variables from left to right while preserving answer .
During that greedy deletion, the cut is the rightmost variable you were forced to keep once the current prefix alone still gives . That is a true child boundary. Recurse on the two sides with the other side fixed to the neutral value. Query count is , which is totally fine for .
The huge value range is bait. We only query values and and subtract from the answer, so every query is a Boolean query:
So the unknown function is an ordered read-once monotone Boolean formula. Every subtree owns a contiguous interval of variables. We will recover some equivalent tree; Bowser does not care whether our parentheses match his, only whether the function matches.
Important for hacks: this is still an interactive solution. The hack expression is read by the interactor, not by your program.
Suppose we are learning the restricted function on , while all variables outside are fixed to values that make ancestor siblings neutral. First determine whether the top operation is or .
Let be the answer when are and are . Let be the first with . Let be the answer when are and are . Let be the last with .
If the root is , a left child and a right child can independently make the answer , so . If the root is , making the answer requires all top children, so any successful prefix must reach far right and any successful suffix must reach far left, giving . Thus:
Now write for a root and for a root. The other value is neutral for that root operation. The root outputs iff at least one top child outputs .
We need a cut such that
The annoying part is that the first prefix producing can stop inside the first top child. Example: becomes already at prefix , not at the true boundary after . Cute trap. Also rude.
Set every variable in to . Find , the first position where setting to makes the answer . Then find , the first position where setting to makes the answer . The block contains enough variables to activate the first top child, but not enough to activate a later top child.
Start with set to and everything else set to . Sweep left to right. Try changing the current variable back to :
After each step, also test whether the current prefix alone still gives after all variables to its right are reset to . When this first happens, the latest forced variable is exactly the right end of the first top child, so it is a valid cut .
Why? The greedy deletion pushes every witness as far right as possible. In a same-operation subtree, one child is enough, so the witness slides right. In an opposite-operation subtree, every child is needed, so the rightmost child contributes a forced variable. Inductively, the last forced variable lands on the subtree boundary. Sounds magical; it is just min/max being brutally monotone.
After finding , recurse on and , passing defaults where the other side is neutral. This isolates the child function exactly. A leaf interval is just .
After printing !, evaluate the recovered tree on Bowser's real inputs using ordinary and .
For an interval of length , we use queries for binary searches and queries for the greedy sweep. Over a binary recursion tree on ordered leaves,
so the total query count is . With , this is comfortably below ; no sweaty nonsense required.
Reference checked while writing: official Codeforces editorial.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int n;
struct Node {
int l = 0, r = 0;
int op = -1; // 1 = max, 0 = min, -1 = leaf
int left = -1, right = -1;
};
vector<Node> tr;
int readInt() {
int x;
if (!(cin >> x)) exit(0);
if (x == -1) exit(0);
return x;
}
int ask(const vector<int>& a) {
cout << "?";
for (int i = 1; i <= n; i++) cout << ' ' << a[i] + 1;
cout << '\n';
cout.flush();
int ans;
if (!(cin >> ans)) exit(0);
if (ans == -1) exit(0);
return ans - 1;
}
int addLeaf(int l) {
tr.push_back(Node{l, l, -1, -1, -1});
return (int)tr.size() - 1;
}
int addNode(int l, int r, int op, int left, int right) {
tr.push_back(Node{l, r, op, left, right});
return (int)tr.size() - 1;
}
int bsFwd(int L, int R, int val, const vector<int>& defaults) {
int lo = L, hi = R;
while (lo < hi) {
int mid = (lo + hi) / 2;
vector<int> q = defaults;
for (int i = L; i <= mid; i++) q[i] = val;
for (int i = mid + 1; i <= R; i++) q[i] = 1 - val;
if (ask(q) == val) hi = mid;
else lo = mid + 1;
}
return lo;
}
int bsRev(int L, int R, int val, const vector<int>& defaults) {
int lo = L, hi = R;
while (lo < hi) {
int mid = (lo + hi + 1) / 2;
vector<int> q = defaults;
for (int i = L; i < mid; i++) q[i] = 1 - val;
for (int i = mid; i <= R; i++) q[i] = val;
if (ask(q) == val) lo = mid;
else hi = mid - 1;
}
return lo;
}
int recover(int L, int R, vector<int> defaults) {
if (L == R) return addLeaf(L);
int s = bsFwd(L, R, 1, defaults);
int t = bsRev(L, R, 1, defaults);
int up = (s < t ? 1 : 0);
int neutral = 1 - up;
vector<int> base = defaults;
for (int i = L; i <= R; i++) base[i] = neutral;
int p = bsFwd(L, R, up, base);
if (p == R) exit(0);
int q = bsFwd(p + 1, R, up, base);
vector<int> cur = base;
for (int i = L; i < q; i++) cur[i] = up;
int lastKept = -1, cut = -1;
for (int i = L; i < q; i++) {
cur[i] = neutral;
if (ask(cur) != up) {
cur[i] = up;
lastKept = i;
}
if (lastKept != -1) {
vector<int> check = cur;
for (int j = i + 1; j <= R; j++) check[j] = neutral;
if (ask(check) == up) {
cut = lastKept;
break;
}
}
}
if (cut < L || cut >= R) exit(0);
int left = recover(L, cut, base);
int right = recover(cut + 1, R, base);
return addNode(L, R, up, left, right);
}
ll eval(int v, const vector<ll>& x) {
const Node& node = tr[v];
if (node.op == -1) return x[node.l];
ll a = eval(node.left, x);
ll b = eval(node.right, x);
return node.op ? max(a, b) : min(a, b);
}
int main() {
setIO();
int T = readInt();
while (T--) {
n = readInt();
tr.clear();
vector<int> defaults(n + 1, 0);
int root = recover(1, n, defaults);
cout << "!" << '\n';
cout.flush();
while (true) {
vector<ll> x(n + 1);
ll first;
if (!(cin >> first)) return 0;
if (first == -1) return 0;
if (first == 0) break;
x[1] = first;
for (int i = 2; i <= n; i++) {
cin >> x[i];
if (x[i] == -1) return 0;
}
cout << eval(root, x) << '\n';
cout.flush();
}
}
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int n;
struct Node {
int l = 0, r = 0;
int op = -1; // 1 = max, 0 = min, -1 = leaf
int left = -1, right = -1;
};
vector<Node> tr;
int readInt() {
int x;
if (!(cin >> x)) exit(0);
if (x == -1) exit(0);
return x;
}
int ask(const vector<int>& a) {
cout << "?";
for (int i = 1; i <= n; i++) cout << ' ' << a[i] + 1;
cout << '\n';
cout.flush();
int ans;
if (!(cin >> ans)) exit(0);
if (ans == -1) exit(0);
return ans - 1;
}
int addLeaf(int l) {
tr.push_back(Node{l, l, -1, -1, -1});
return (int)tr.size() - 1;
}
int addNode(int l, int r, int op, int left, int right) {
tr.push_back(Node{l, r, op, left, right});
return (int)tr.size() - 1;
}
int bsFwd(int L, int R, int val, const vector<int>& defaults) {
int lo = L, hi = R;
while (lo < hi) {
int mid = (lo + hi) / 2;
vector<int> q = defaults;
for (int i = L; i <= mid; i++) q[i] = val;
for (int i = mid + 1; i <= R; i++) q[i] = 1 - val;
if (ask(q) == val) hi = mid;
else lo = mid + 1;
}
return lo;
}
int bsRev(int L, int R, int val, const vector<int>& defaults) {
int lo = L, hi = R;
while (lo < hi) {
int mid = (lo + hi + 1) / 2;
vector<int> q = defaults;
for (int i = L; i < mid; i++) q[i] = 1 - val;
for (int i = mid; i <= R; i++) q[i] = val;
if (ask(q) == val) lo = mid;
else hi = mid - 1;
}
return lo;
}
int recover(int L, int R, vector<int> defaults) {
if (L == R) return addLeaf(L);
int s = bsFwd(L, R, 1, defaults);
int t = bsRev(L, R, 1, defaults);
int up = (s < t ? 1 : 0);
int neutral = 1 - up;
vector<int> base = defaults;
for (int i = L; i <= R; i++) base[i] = neutral;
int p = bsFwd(L, R, up, base);
if (p == R) exit(0);
int q = bsFwd(p + 1, R, up, base);
vector<int> cur = base;
for (int i = L; i < q; i++) cur[i] = up;
int lastKept = -1, cut = -1;
for (int i = L; i < q; i++) {
cur[i] = neutral;
if (ask(cur) != up) {
cur[i] = up;
lastKept = i;
}
if (lastKept != -1) {
vector<int> check = cur;
for (int j = i + 1; j <= R; j++) check[j] = neutral;
if (ask(check) == up) {
cut = lastKept;
break;
}
}
}
if (cut < L || cut >= R) exit(0);
int left = recover(L, cut, base);
int right = recover(cut + 1, R, base);
return addNode(L, R, up, left, right);
}
ll eval(int v, const vector<ll>& x) {
const Node& node = tr[v];
if (node.op == -1) return x[node.l];
ll a = eval(node.left, x);
ll b = eval(node.right, x);
return node.op ? max(a, b) : min(a, b);
}
int main() {
setIO();
int T = readInt();
while (T--) {
n = readInt();
tr.clear();
vector<int> defaults(n + 1, 0);
int root = recover(1, n, defaults);
cout << "!" << '\n';
cout.flush();
while (true) {
vector<ll> x(n + 1);
ll first;
if (!(cin >> first)) return 0;
if (first == -1) return 0;
if (first == 0) break;
x[1] = first;
for (int i = 2; i <= n; i++) {
cin >> x[i];
if (x[i] == -1) return 0;
}
cout << eval(root, x) << '\n';
cout.flush();
}
}
}