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.
For one fixed value , you do not need to find both positions. If one split of the positions has on both sides, then is definitely not the singleton.
Do one Version-1-style split first: randomly divide all positions into two parts, and classify every value as left-only, right-only, or split between both.
Pretend every value appears twice. In that fake count, the side containing the real singleton is exactly the side whose predicted length is one too large.
After that first split, only values fully inside the chosen side can still be the answer. A random half keeps only about a quarter of the doubled values, so the candidate list becomes small.
For each candidate, binary-search over a random ordering of the chosen side. A doubled value gets rejected as soon as its two copies land in different halves; the real singleton is the only one that can reach a single position.
This is an interactive randomized solution. The important detail is that the interactor is not adaptive: the hidden array is fixed before our queries. So random partitions behave like actual random partitions, not like some evil wizard moving the answer around after every query.
First cut: shrink the problem hard
Let . Randomly shuffle the positions and split them into two parts and of sizes and .
For every value :
Now is classified as:
Here is the Version-1 parity/count trick. Pretend every value appears twice. Then:
For every normal value this fake count matches reality. For the singleton, the fake count overcounts its side by exactly , because the singleton has one real occurrence but we pretend it has two.
So exactly one side has predicted size = real size + 1. That side contains the singleton. Keep that side as pool, and keep only the values that were wholly inside it as candidates.
Why is this good? For a doubled value, both copies land in the same random half with probability about . So after one split, the candidate list is usually about , around values. The first layer costs about queries.
How to reject one candidate cheaply
Now take one candidate . We know all real occurrences of are inside pool:
pool;pool.Shuffle pool once. We run a binary-search-like procedure on this order.
Maintain a segment that contains all occurrences of . Split it into two halves and .
If we reach a segment of size , then must be the singleton. A doubled value cannot fit two different positions into one leaf; it would have been split at some earlier binary partition. Brutal, clean, done.
Expected query count
For a doubled candidate at a balanced split:
So if is the expected number of queries to reject a doubled candidate,
.
The true singleton reaches a leaf, costing at most about queries. We also shuffle the candidate order, so on average we find the singleton before checking all candidates.
Correctness
The first phase keeps the correct side because only the singleton causes the fake doubled-count to exceed the real side size by .
Every value not wholly inside that side cannot be the singleton: if it appears on both sides, it has at least two occurrences; if it is wholly outside, it is obviously irrelevant.
For each remaining candidate, the binary procedure is exact: a doubled value is rejected once its two positions fall into different halves, while the singleton never appears in both halves. Therefore the first candidate that reaches a leaf is exactly the hidden single.
Randomness only controls the query count, not the logical correctness once the queries are answered. Since the hidden array is fixed and , this stays under the -query cap with very high probability.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
mt19937 rng((uint64_t)chrono::steady_clock::now().time_since_epoch().count());
int askValue(int x, const vector<int>& s) {
cout << "? " << x << " " << s.size();
for (int p : s) cout << " " << p;
cout << endl;
int res;
if (!(cin >> res)) exit(0);
if (res == -1) exit(0);
return res;
}
void answerValue(int y) {
cout << "! " << y << endl;
}
vector<int> slice(const vector<int>& v, int l, int r) {
return vector<int>(v.begin() + l, v.begin() + r);
}
bool isSingleCandidate(int x, const vector<int>& order) {
int l = 0, r = (int)order.size();
while (r - l > 1) {
int m = (l + r) / 2;
vector<int> left = slice(order, l, m);
if (!askValue(x, left)) {
l = m;
continue;
}
vector<int> right = slice(order, m, r);
if (askValue(x, right)) return false;
r = m;
}
return true;
}
void solveCase(int n) {
int m = 2 * n - 1;
vector<int> pos(m);
iota(pos.begin(), pos.end(), 1);
shuffle(pos.begin(), pos.end(), rng);
vector<int> left(pos.begin(), pos.begin() + m / 2);
vector<int> right(pos.begin() + m / 2, pos.end());
vector<int> candLeft, candRight;
int predLeft = 0, predRight = 0;
for (int x = 1; x <= n; x++) {
int inLeft = askValue(x, left);
if (inLeft) {
int inRight = askValue(x, right);
if (inRight) {
predLeft++;
predRight++;
} else {
predLeft += 2;
candLeft.push_back(x);
}
} else {
predRight += 2;
candRight.push_back(x);
}
}
vector<int> pool, candidates;
if (predLeft != (int)left.size()) {
pool = left;
candidates = candLeft;
} else {
pool = right;
candidates = candRight;
}
shuffle(pool.begin(), pool.end(), rng);
shuffle(candidates.begin(), candidates.end(), rng);
for (int x : candidates) {
if (isSingleCandidate(x, pool)) {
answerValue(x);
return;
}
}
answerValue(candidates.empty() ? 1 : candidates.back());
}
int main() {
setIO();
int T;
if (!(cin >> T)) return 0;
while (T--) {
int n;
cin >> n;
if (n == -1) return 0;
solveCase(n);
}
return 0;
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
mt19937 rng((uint64_t)chrono::steady_clock::now().time_since_epoch().count());
int askValue(int x, const vector<int>& s) {
cout << "? " << x << " " << s.size();
for (int p : s) cout << " " << p;
cout << endl;
int res;
if (!(cin >> res)) exit(0);
if (res == -1) exit(0);
return res;
}
void answerValue(int y) {
cout << "! " << y << endl;
}
vector<int> slice(const vector<int>& v, int l, int r) {
return vector<int>(v.begin() + l, v.begin() + r);
}
bool isSingleCandidate(int x, const vector<int>& order) {
int l = 0, r = (int)order.size();
while (r - l > 1) {
int m = (l + r) / 2;
vector<int> left = slice(order, l, m);
if (!askValue(x, left)) {
l = m;
continue;
}
vector<int> right = slice(order, m, r);
if (askValue(x, right)) return false;
r = m;
}
return true;
}
void solveCase(int n) {
int m = 2 * n - 1;
vector<int> pos(m);
iota(pos.begin(), pos.end(), 1);
shuffle(pos.begin(), pos.end(), rng);
vector<int> left(pos.begin(), pos.begin() + m / 2);
vector<int> right(pos.begin() + m / 2, pos.end());
vector<int> candLeft, candRight;
int predLeft = 0, predRight = 0;
for (int x = 1; x <= n; x++) {
int inLeft = askValue(x, left);
if (inLeft) {
int inRight = askValue(x, right);
if (inRight) {
predLeft++;
predRight++;
} else {
predLeft += 2;
candLeft.push_back(x);
}
} else {
predRight += 2;
candRight.push_back(x);
}
}
vector<int> pool, candidates;
if (predLeft != (int)left.size()) {
pool = left;
candidates = candLeft;
} else {
pool = right;
candidates = candRight;
}
shuffle(pool.begin(), pool.end(), rng);
shuffle(candidates.begin(), candidates.end(), rng);
for (int x : candidates) {
if (isSingleCandidate(x, pool)) {
answerValue(x);
return;
}
}
answerValue(candidates.empty() ? 1 : candidates.back());
}
int main() {
setIO();
int T;
if (!(cin >> T)) return 0;
while (T--) {
int n;
cin >> n;
if (n == -1) return 0;
solveCase(n);
}
return 0;
}