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.
Treat one response as more than a random bit string: the 1 vertices form the greedy maximal independent set produced by the given order. Thus no two 1 vertices are adjacent, and every 0 vertex has a neighbor among the earlier 1 vertices.
Suppose and are both known independent sets. Query all vertices of first, then all vertices of . Every vertex of is selected, and vertices of cannot interfere with one another, so a vertex returns 0 exactly when it has at least one neighbor in . This gives the set in one query.
Now assume are independent and every vertex of has a neighbor in . To recover every edge between them, split . Find . Every vertex in must neighbor ; among , separately find those also neighboring . This constructs the exact candidate sets for two recursive calls. When , every candidate in is adjacent to that sole vertex.
For an arbitrary current vertex set , query and split it into selected vertices and rejected vertices . There are no edges inside , so first recursively recover the forest induced by . Once that is known, 2-color every component of ; each color class is independent. Use the independent-set neighborhood primitive and the divide-and-conquer routine to recover all edges from each color class to .
The recursion is correct because every edge of is either inside or between and ; an - edge is impossible. For the budget, assign each vertex the outer recursion layer where it first becomes selected. Every earlier layer that rejected it supplies a distinct incident tree edge, so the total number of extra layer appearances is at most . The cross-edge routines split sets in half, have depth at most , and duplicated candidates can be charged to distinct tree edges. The resulting exact accounting stays below .
This is still an interactive problem. The hack format describes the hidden tree supplied to the interactor; your program itself reads only and each . Reading the next edges would be wonderfully convenient and completely wrong.
For a queried sequence, the returned 1 vertices form the greedy independent set obtained by processing that order.
Two immediate facts matter:
So a query partitions a set into a known independent set and the remaining vertices. That is already useful, but we need a more precise tool.
Let and be disjoint independent sets. Query the concatenation
Because is independent, every vertex of is selected. Now consider :
Hence the zeroes in the part of the response are exactly
Call this operation touching(A, B). The slightly cursed detail is that the vertices we want are represented by zeroes, not ones.
We next solve this subproblem:
Let this routine be recoverCross(A, B).
If , every vertex of must be adjacent to the sole vertex of . We can report all those edges immediately, with no query.
Split into two nearly equal halves and .
First compute
using touching(A_L, B). These are exactly the candidates for the left recursive call.
What belongs to the right call?
touching(A_R, B_L).Therefore
Now recurse on and .
The invariant is preserved: every candidate supplied to a child has a neighbor in that child's set. At singleton leaves, the invariant identifies every individual edge.
Let build(U) recover every hidden edge whose endpoints both lie in .
Query all of . Let
1;0.The set is independent, so the edges of the forest induced by fall into only two categories:
There are no edges inside .
First call build(R). This recovers the entire forest induced by .
We cannot directly use touching(R,I), because need not be independent. However, its now-known graph is a forest, so we can 2-color every connected component. Let the two global color classes be and . Each class is independent, even across different components.
For each :
touching(C_j,I).recoverCross(C_j,B_j).Since , this recovers every edge between and . Together with the recursive result inside , all edges in are now known.
The recursion terminates because the first queried vertex is always selected, so is strictly smaller than .
We prove by induction on that build(U) recovers exactly the edges induced by .
For , there are no edges.
For a larger , its query produces and .
build(R) recovers exactly all - edges.touching(C_j,I) returns exactly the vertices of adjacent to .recoverCross recovers exactly every edge between those two independent sets.Thus every - edge is recovered, no nonexistent edge is inserted, and all three possible endpoint categories are handled. The induction follows.
The relevant cost is the sum of sequence lengths, not merely the number of queries.
Consider the chain of outer build calls. Give each vertex a layer equal to the call where it first enters the selected set . If a vertex survives through earlier layers, then in each such layer it was rejected by a neighbor selected in that layer. These neighbors are distinct, and the corresponding tree edges can each be charged only once. Therefore
Consequently:
build queries have total length at most ;For recoverCross, each split halves , so there are at most
split levels. At any fixed split level, an vertex occurs in one current block. If a vertex occurs in several blocks, every additional occurrence is forced by a different incident tree edge; the same charging handles the second overlap query. The cross-edge sets from different outer layers are edge-disjoint. Carrying this level-by-level charge through the exact two-query split, while noting that singleton blocks issue no query, gives less than symbols for all cross-edge recovery calls.
Together with the less than outer cost, the total is at most .
endl, which flushes immediately.-1 or input closes, terminate at once.The local running time is apart from tiny vector-management constants, and the interaction uses at most queried vertices.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int n;
vector<vector<int>> graphFound;
vector<int> seen;
int seenToken;
string query(const vector<int>& vertices) {
cout << "? " << vertices.size();
for (int v : vertices) cout << ' ' << v + 1;
cout << endl;
string response;
if (!(cin >> response) || response == "-1") exit(0);
return response;
}
vector<int> touching(const vector<int>& a, const vector<int>& b) {
if (a.empty() || b.empty()) return {};
vector<int> order;
order.reserve(a.size() + b.size());
order.insert(order.end(), a.begin(), a.end());
order.insert(order.end(), b.begin(), b.end());
string response = query(order);
vector<int> result;
for (int i = 0; i < (int)b.size(); ++i) {
if (response[a.size() + i] == '0') result.push_back(b[i]);
}
return result;
}
void addEdge(int u, int v) {
graphFound[u].push_back(v);
graphFound[v].push_back(u);
}
void recoverCross(const vector<int>& a, const vector<int>& b) {
if (a.empty() || b.empty()) return;
if (a.size() == 1) {
for (int v : b) addEdge(a[0], v);
return;
}
int mid = a.size() / 2;
vector<int> left(a.begin(), a.begin() + mid);
vector<int> right(a.begin() + mid, a.end());
vector<int> leftB = touching(left, b);
int token = ++seenToken;
for (int v : leftB) seen[v] = token;
vector<int> rightB;
for (int v : b) {
if (seen[v] != token) rightB.push_back(v);
}
vector<int> both = touching(right, leftB);
rightB.insert(rightB.end(), both.begin(), both.end());
recoverCross(left, leftB);
recoverCross(right, rightB);
}
void build(const vector<int>& vertices) {
if (vertices.size() <= 1) return;
string response = query(vertices);
vector<int> selected, rejected;
for (int i = 0; i < (int)vertices.size(); ++i) {
(response[i] == '1' ? selected : rejected).push_back(vertices[i]);
}
build(rejected);
vector<int> color(n, -1);
array<vector<int>, 2> part;
for (int start : rejected) {
if (color[start] != -1) continue;
queue<int> q;
q.push(start);
color[start] = 0;
while (!q.empty()) {
int u = q.front();
q.pop();
part[color[u]].push_back(u);
for (int v : graphFound[u]) {
if (color[v] == -1) {
color[v] = color[u] ^ 1;
q.push(v);
}
}
}
}
for (int side = 0; side < 2; ++side) {
vector<int> candidates = touching(part[side], selected);
recoverCross(part[side], candidates);
}
}
void solveCase() {
graphFound.assign(n, {});
seen.assign(n, 0);
seenToken = 0;
vector<int> all(n);
iota(all.begin(), all.end(), 0);
build(all);
cout << "!\n";
for (int u = 0; u < n; ++u) {
for (int v : graphFound[u]) {
if (u < v) cout << u + 1 << ' ' << v + 1 << '\n';
}
}
cout.flush();
}
int main() {
setIO();
int t;
cin >> t;
while (t--) {
cin >> n;
solveCase();
}
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int n;
vector<vector<int>> graphFound;
vector<int> seen;
int seenToken;
string query(const vector<int>& vertices) {
cout << "? " << vertices.size();
for (int v : vertices) cout << ' ' << v + 1;
cout << endl;
string response;
if (!(cin >> response) || response == "-1") exit(0);
return response;
}
vector<int> touching(const vector<int>& a, const vector<int>& b) {
if (a.empty() || b.empty()) return {};
vector<int> order;
order.reserve(a.size() + b.size());
order.insert(order.end(), a.begin(), a.end());
order.insert(order.end(), b.begin(), b.end());
string response = query(order);
vector<int> result;
for (int i = 0; i < (int)b.size(); ++i) {
if (response[a.size() + i] == '0') result.push_back(b[i]);
}
return result;
}
void addEdge(int u, int v) {
graphFound[u].push_back(v);
graphFound[v].push_back(u);
}
void recoverCross(const vector<int>& a, const vector<int>& b) {
if (a.empty() || b.empty()) return;
if (a.size() == 1) {
for (int v : b) addEdge(a[0], v);
return;
}
int mid = a.size() / 2;
vector<int> left(a.begin(), a.begin() + mid);
vector<int> right(a.begin() + mid, a.end());
vector<int> leftB = touching(left, b);
int token = ++seenToken;
for (int v : leftB) seen[v] = token;
vector<int> rightB;
for (int v : b) {
if (seen[v] != token) rightB.push_back(v);
}
vector<int> both = touching(right, leftB);
rightB.insert(rightB.end(), both.begin(), both.end());
recoverCross(left, leftB);
recoverCross(right, rightB);
}
void build(const vector<int>& vertices) {
if (vertices.size() <= 1) return;
string response = query(vertices);
vector<int> selected, rejected;
for (int i = 0; i < (int)vertices.size(); ++i) {
(response[i] == '1' ? selected : rejected).push_back(vertices[i]);
}
build(rejected);
vector<int> color(n, -1);
array<vector<int>, 2> part;
for (int start : rejected) {
if (color[start] != -1) continue;
queue<int> q;
q.push(start);
color[start] = 0;
while (!q.empty()) {
int u = q.front();
q.pop();
part[color[u]].push_back(u);
for (int v : graphFound[u]) {
if (color[v] == -1) {
color[v] = color[u] ^ 1;
q.push(v);
}
}
}
}
for (int side = 0; side < 2; ++side) {
vector<int> candidates = touching(part[side], selected);
recoverCross(part[side], candidates);
}
}
void solveCase() {
graphFound.assign(n, {});
seen.assign(n, 0);
seenToken = 0;
vector<int> all(n);
iota(all.begin(), all.end(), 0);
build(all);
cout << "!\n";
for (int u = 0; u < n; ++u) {
for (int v : graphFound[u]) {
if (u < v) cout << u + 1 << ' ' << v + 1 << '\n';
}
}
cout.flush();
}
int main() {
setIO();
int t;
cin >> t;
while (t--) {
cin >> n;
solveCase();
}
}