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.
Don’t try to find positions of every value. That burns queries way too fast. Instead, look at an interval and ask: “if every value appeared twice, what length should this interval have?”
Split the current interval into left and right. For each relevant value, learn whether it appears on the left, the right, or both.
Keep two kinds of values for the current interval: values whose all remaining occurrences are inside it, and values that have exactly one occurrence inside it and another occurrence outside it.
For a child interval, values fully inside it should contribute positions, while crossing values should contribute position. If the child has no singleton, this predicted length equals the real length.
The child containing the singleton is the only one whose predicted length is too large by exactly , because the singleton value is counted as if it appeared twice. Recurse there.
The nasty part is that the query only answers existence, not count. So the obvious “count each value” plan is dead on arrival. The trick is to stop asking for exact counts and instead use a parity-ish contradiction: pretend the singleton also appeared twice.
State
We solve recursively on an interval of positions that is guaranteed to contain the singleton position.
For this interval, maintain two lists:
vals: values whose all occurrences are inside .waste: values that have exactly one occurrence inside and another occurrence outside it.At the start, , every value is fully inside, so:
The singleton value is always inside vals, because its only occurrence is inside the interval we recurse into.
Splitting an interval
Let
We split into:
Now classify every relevant value for the two children.
For a value in waste, we know it appears exactly once in the current interval. So one query on the left half is enough:
waste for ;waste for .For a value in vals, all its occurrences are inside the current interval. Query the left half:
vals of ;waste of both children;vals of .That is at most queries for a vals value and query for a waste value.
How do we know which half contains the answer?
For a child interval , suppose we have built its lists childVals and childWaste.
If every value appeared twice, then:
childVals would contribute positions inside ;childWaste would contribute position inside .So the predicted length is:
The real length is just:
If does not contain the singleton, then all values behave normally, and:
If does contain the singleton, then the singleton value is in childVals, but it contributes only real position, while our fake count adds . So:
That is the whole damn problem. The wrong-length half is the half containing the singleton. Recurse there.
Stopping
If vals has size , that one value must be the singleton. Why? The true singleton is always in vals, and there is only one candidate left. No drama.
Query bound
For an interval of length , the invariant gives:
because every value in vals contributes positions except the singleton, which contributes , and every value in waste contributes .
In one recursive step, the number of queries is at most:
The visited interval lengths shrink by halves, starting from . Their total is below about , and there are only recursive levels. More precisely, this fits inside the required
queries.
So the strategy is deterministic, clean, and query-limit friendly. No gambling, no “hope the judge is chill” nonsense.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int ask_interval(int x, int l, int r) {
cout << "? " << x << ' ' << (r - l + 1);
for (int i = l; i <= r; i++) cout << ' ' << i;
cout << endl;
cout.flush();
int res;
cin >> res;
if (res == -1) exit(0);
return res;
}
int solve(int l, int r, vector<int> vals, vector<int> waste) {
if ((int)vals.size() == 1) return vals[0];
int mid = (l + r) / 2;
vector<int> leftVals, rightVals, leftWaste, rightWaste;
for (int x : waste) {
if (ask_interval(x, l, mid)) leftWaste.push_back(x);
else rightWaste.push_back(x);
}
for (int x : vals) {
int inLeft = ask_interval(x, l, mid);
if (!inLeft) {
rightVals.push_back(x);
continue;
}
int inRight = ask_interval(x, mid + 1, r);
if (inRight) {
leftWaste.push_back(x);
rightWaste.push_back(x);
} else {
leftVals.push_back(x);
}
}
int leftLen = mid - l + 1;
int leftPred = 2 * (int)leftVals.size() + (int)leftWaste.size();
if (leftPred != leftLen) {
return solve(l, mid, leftVals, leftWaste);
}
return solve(mid + 1, r, rightVals, rightWaste);
}
int main() {
setIO();
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
if (n == -1) return 0;
vector<int> vals(n), waste;
iota(vals.begin(), vals.end(), 1);
int ans = solve(1, 2 * n - 1, vals, waste);
cout << "! " << ans << endl;
cout.flush();
}
return 0;
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int ask_interval(int x, int l, int r) {
cout << "? " << x << ' ' << (r - l + 1);
for (int i = l; i <= r; i++) cout << ' ' << i;
cout << endl;
cout.flush();
int res;
cin >> res;
if (res == -1) exit(0);
return res;
}
int solve(int l, int r, vector<int> vals, vector<int> waste) {
if ((int)vals.size() == 1) return vals[0];
int mid = (l + r) / 2;
vector<int> leftVals, rightVals, leftWaste, rightWaste;
for (int x : waste) {
if (ask_interval(x, l, mid)) leftWaste.push_back(x);
else rightWaste.push_back(x);
}
for (int x : vals) {
int inLeft = ask_interval(x, l, mid);
if (!inLeft) {
rightVals.push_back(x);
continue;
}
int inRight = ask_interval(x, mid + 1, r);
if (inRight) {
leftWaste.push_back(x);
rightWaste.push_back(x);
} else {
leftVals.push_back(x);
}
}
int leftLen = mid - l + 1;
int leftPred = 2 * (int)leftVals.size() + (int)leftWaste.size();
if (leftPred != leftLen) {
return solve(l, mid, leftVals, leftWaste);
}
return solve(mid + 1, r, rightVals, rightWaste);
}
int main() {
setIO();
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
if (n == -1) return 0;
vector<int> vals(n), waste;
iota(vals.begin(), vals.end(), 1);
int ans = solve(1, 2 * n - 1, vals, waste);
cout << "! " << ans << endl;
cout.flush();
}
return 0;
}