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.
Ask about one starting vertex while allowing all portals. That answer is not random trivia: it is exactly the longest path length starting from that vertex in the whole DAG.
If you do that for every vertex, the largest answer is the global longest path length. Any vertex with that largest value can be the first vertex of your answer.
Group vertices by their full-graph answer . If the current vertex has , then the next vertex on a longest path must have .
To test whether a candidate is a valid next vertex, query only the set starting from . The answer is exactly when there is a direct edge .
Scan buckets downward: after choosing a vertex with value , search the bucket with value . Each vertex gets tested at most once, so the total is big queries plus at most tiny queries.
First, do not solve the wrong problem
The adjacency-list format in the hacks section is for the interactor data. Your submitted program still plays the interactive game: it reads , then each , asks queries, reads replies, and finally prints a path. If you try to read all edges directly, you are doing DAG DP for a problem Steve never actually handed you. Classic portal-to-the-wrong-dimension mistake.
So the real task is: with at most queries, recover any longest directed path in a hidden DAG.
The Useful Number
For every vertex , define as the length of the longest path starting at in the whole graph.
We can get directly with one query:
? v n 1 2 ... n
The set contains every vertex, so the answer is exactly the longest path starting from without restrictions. Do this for all .
That costs queries.
Let . Then is the length of a longest path in the entire graph, because every path has some first vertex. Pick any vertex start with ; it can begin a maximum path.
How To Find The Next Vertex
Suppose we are currently at a vertex with .
If , we are done. This vertex cannot reach anything useful for a longer path.
Otherwise, there exists some longest path starting at :
This path has vertices, so the suffix starting at has vertices. Therefore .
But cannot be at least , because then we could take the edge and then a length- path from , giving a length- path from . That contradicts .
So the next vertex on some longest path must satisfy:
.
This is the whole trick. The first round of queries does not only find the start; it also gives every vertex a layer number. A longest path walks through layers .
Testing A Candidate
Now we know the next vertex must be in the bucket of vertices with value . But which one?
For a candidate , ask:
? cur 2 cur y
Only two vertices are allowed. Starting from , the longest possible path inside this set has length iff there is a direct edge . If there is no such edge, the answer is .
So scan all vertices with until one gives answer . Append it to the path, set , and continue.
This is not guessing. A valid candidate is guaranteed to exist, because has a length- path, and the first edge of that path goes to some vertex with value .
Why The Query Limit Works
The first phase uses exactly queries.
In the second phase, we scan buckets . These buckets are disjoint. A vertex belongs to exactly one bucket, so it is tested at most once across the whole reconstruction.
Therefore the second phase uses at most queries.
Total:
That fits under the limit with one query to spare, because apparently Steve is generous but not that generous.
Algorithm
Complexity
The program makes at most queries per test case. The local work is tiny: output size in the first phase because each full-set query prints vertices, and memory.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int main() {
setIO();
int T;
cin >> T;
auto ask = [](int x, const vector<int>& s) {
cout << "? " << x << ' ' << s.size();
for (int v : s) cout << ' ' << v;
cout << endl;
int ans;
if (!(cin >> ans)) exit(0);
if (ans == -1) exit(0);
return ans;
};
while (T--) {
int n;
cin >> n;
if (n == -1) return 0;
vector<int> all(n);
iota(all.begin(), all.end(), 1);
vector<int> f(n + 1);
vector<vector<int>> bucket(n + 1);
int best = 0, start = 1;
for (int v = 1; v <= n; v++) {
f[v] = ask(v, all);
bucket[f[v]].push_back(v);
if (f[v] > best) {
best = f[v];
start = v;
}
}
vector<int> path;
path.push_back(start);
int cur = start;
for (int need = best - 1; need >= 1; need--) {
for (int cand : bucket[need]) {
vector<int> two = {cur, cand};
if (ask(cur, two) == 2) {
path.push_back(cand);
cur = cand;
break;
}
}
}
cout << "! " << path.size();
for (int v : path) cout << ' ' << v;
cout << endl;
}
return 0;
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int main() {
setIO();
int T;
cin >> T;
auto ask = [](int x, const vector<int>& s) {
cout << "? " << x << ' ' << s.size();
for (int v : s) cout << ' ' << v;
cout << endl;
int ans;
if (!(cin >> ans)) exit(0);
if (ans == -1) exit(0);
return ans;
};
while (T--) {
int n;
cin >> n;
if (n == -1) return 0;
vector<int> all(n);
iota(all.begin(), all.end(), 1);
vector<int> f(n + 1);
vector<vector<int>> bucket(n + 1);
int best = 0, start = 1;
for (int v = 1; v <= n; v++) {
f[v] = ask(v, all);
bucket[f[v]].push_back(v);
if (f[v] > best) {
best = f[v];
start = v;
}
}
vector<int> path;
path.push_back(start);
int cur = start;
for (int need = best - 1; need >= 1; need--) {
for (int cand : bucket[need]) {
vector<int> two = {cur, cand};
if (ask(cur, two) == 2) {
path.push_back(cand);
cur = cand;
break;
}
}
}
cout << "! " << path.size();
for (int v : path) cout << ' ' << v;
cout << endl;
}
return 0;
}