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.
Ignore the extra edges at first. A spanning tree is enough: if you can complete the graph using only paths that exist in a spanning tree plus edges you add, all original extra edges are just free shortcuts you do not even need.
Use the first operation with . For every pair at distance in the spanning tree, the middle vertex gives a valid length- path, so after this operation every tree-distance or pair is an edge.
After the first operation, you can move along the square of the tree: one printed step may advance by either or edges in the tree. Let be the tree diameter in edges and set . The second operation should use paths with exactly edges, so its printed is .
If two vertices are at tree distance at least , the path is easy: take their unique tree path and split its length into exactly chunks, each chunk being size or . Size- chunks are valid because operation one already created those shortcut edges.
The only annoying case is when the direct tree path is too short. Use the diameter as spare runway. On a line whose square is available, an edge can be replaced by a zigzag of any larger length, consuming unused vertices to the left or right. That lets you waste exactly the missing number of steps without repeating vertices.
We only need to construct paths; we are not asked to minimize the number of printed paths or the chosen lengths. So the sane move is to build a controlled graph inside the given graph.
Step 1: throw away extra edges
Take any spanning tree of the input graph. Every edge of already exists in the graph. Extra original edges are useful only because they are already present, but the construction does not need them.
Let be distance in this tree.
First operation
Use , so every printed path has length .
For every missing pair with , let be the middle vertex on the tree path. Print
.
At the end of this operation, every pair with tree distance or is an edge. In other words, we now have the square of the spanning tree. That is the whole trick. The rest is just carefully walking in this squared tree.
Pairs that were already original edges are skipped, because adding duplicates would be illegal. No funny business there.
Second operation target length
Let be the diameter of , measured in edges, and define
.
The second operation will use
,
so every printed path has exactly edges.
Why is the right scale? After operation one, one step can cover at most two tree edges. The farthest pair is distance , so it needs at least printed steps. The nice part is that this is also always enough for every missing pair.
Direct paths
Suppose we need a path from to , and .
Take the unique tree path from to . We need to split its tree edges into exactly moves, where each move has size or .
This is possible because:
Use exactly chunks of size , and the rest chunks of size . Every chunk is an edge in the squared tree, so this gives a valid printed path.
How to waste steps on a line
Now assume the direct tree path is too short. We need extra steps without changing the endpoints and without repeating vertices.
First solve this on a simple line of vertices , where edges exist between positions whose difference is or .
If we need a path from position to position with :
A unit edge can absorb waste on the left. For example, with waste :
.
All jumps have size or , and no vertex repeats. Similar zigzags handle any waste amount, using unused vertices to the left. There is a mirrored version using unused vertices to the right.
So for a longer line path, spend some waste at the first edge using left slack, spend the rest at the last edge using right slack, and keep the middle as ordinary unit steps. This is what the implementation calls linePath.
Using the diameter as the line
Fix a diameter path . For any vertex , let be the closest vertex to on this diameter. This is the projection of onto the diameter.
Now build the required length- path for a missing pair .
Case 1:
Already handled by direct chunking.
Case 2:
Assume is left of on the diameter.
The path is:
The implementation may compress the two branch parts with length- jumps if needed. Then the middle diameter segment gets the remaining required length.
Why is there enough spare room? Since the direct to distance is below , the middle diameter segment is short. The unused diameter pieces to the left of and to the right of together have enough vertices to pay for the missing steps. That is exactly what the line gadget consumes.
Case 3:
Both vertices hang off the same diameter vertex .
Root the tree at .
If is an ancestor of , build the path from to and reverse it.
If is not an ancestor of , move one edge from toward , print that first step, and recursively solve the smaller problem with one fewer required step. This does not repeat vertices, because that first step leaves the old side branch behind.
Eventually is an ancestor of . Then the tree path from either diameter endpoint or to contains . One of or is far enough from because . Use that endpoint-to- path as a line, start at the position of , end at , and apply the same line zigzag construction.
That is the whole construction. The statement looks like graph wizardry, but after operation one it is mostly walking on a squared tree and burning extra length with controlled zigzags. Annoying? Yes. Impossible? Nah.
Correctness sketch
Operation one only prints real length- tree paths, so every added edge is valid. It skips already existing edges, so no duplicate edge is added.
Before operation two, every pair at tree distance at most is an edge. Therefore any sequence where consecutive vertices are tree-distance or is a valid printed path.
For every still-missing edge , the construction returns a simple sequence of exactly vertices from to . Consecutive vertices are always distance or in the tree, either by direct chunking or by the line zigzag gadget. So every operation-two path is valid.
Operation two prints exactly one path for every edge still missing after operation one, and no path for an already existing edge. Since all additions of an operation happen only after all its paths are checked, operation-two paths never rely on operation-two edges. Thus the final graph is complete and simple.
Complexity
, so brute-force tree distances and path reconstruction are completely fine. The implementation is plus output size, which easily fits.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
vector<int> directLine(int s, int t, int len) {
if (s == t) return len == 0 ? vector<int>{s} : vector<int>{};
if (s > t) {
auto res = directLine(t, s, len);
reverse(res.begin(), res.end());
return res;
}
int d = t - s;
if ((d + 1) / 2 > len || len > d) return {};
int twos = d - len;
vector<int> res{s};
int cur = s;
for (int i = 0; i < twos; i++) {
cur += 2;
res.push_back(cur);
}
while (cur < t) {
cur++;
res.push_back(cur);
}
return res;
}
vector<int> leftGadget(int p, int waste) {
if (waste == 0) return {p, p + 1};
vector<int> res{p};
if (waste % 2 == 0) {
for (int x = p - 2; x >= p - waste; x -= 2) res.push_back(x);
res.push_back(p - waste + 1);
for (int x = p - waste + 3; x <= p + 1; x += 2) res.push_back(x);
} else {
for (int x = p - 2; x >= p - (waste - 1); x -= 2) res.push_back(x);
res.push_back(p - waste);
for (int x = p - waste + 2; x <= p + 1; x += 2) res.push_back(x);
}
return res;
}
vector<int> rightGadget(int p, int waste) {
if (waste == 0) return {p, p + 1};
vector<int> res{p};
if (waste % 2 == 1) {
for (int x = p + 2; x <= p + waste + 1; x += 2) res.push_back(x);
res.push_back(p + waste);
for (int x = p + waste - 2; x >= p + 1; x -= 2) res.push_back(x);
} else {
for (int x = p + 2; x <= p + waste; x += 2) res.push_back(x);
res.push_back(p + waste + 1);
for (int x = p + waste - 1; x >= p + 1; x -= 2) res.push_back(x);
}
return res;
}
vector<int> linePath(int D, int s, int t, int len) {
if (s == t) return len == 0 ? vector<int>{s} : vector<int>{};
if (s > t) {
auto res = linePath(D, t, s, len);
reverse(res.begin(), res.end());
return res;
}
auto direct = directLine(s, t, len);
if (!direct.empty()) return direct;
int d = t - s;
if (len < d) return {};
int waste = len - d;
if (d == 1) {
if (waste <= s) return leftGadget(s, waste);
if (waste <= D - t) return rightGadget(s, waste);
return {};
}
int low = max(0, waste - (D - t));
int high = min(waste, s);
if (low > high) return {};
int leftWaste = low;
int rightWaste = waste - leftWaste;
vector<int> res = leftGadget(s, leftWaste);
for (int x = s + 2; x < t; x++) res.push_back(x);
auto tail = rightGadget(t - 1, rightWaste);
res.insert(res.end(), tail.begin() + 1, tail.end());
return res;
}
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n, m;
cin >> n >> m;
vector<vector<int>> orig(n, vector<int>(n, 0));
vector<vector<int>> g(n);
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
--u; --v;
orig[u][v] = orig[v][u] = 1;
g[u].push_back(v);
g[v].push_back(u);
}
if (m == n * (n - 1) / 2) {
cout << 0 << '\n';
continue;
}
vector<vector<int>> tree(n);
vector<int> seen(n, 0);
queue<int> q;
seen[0] = 1;
q.push(0);
while (!q.empty()) {
int u = q.front();
q.pop();
for (int v : g[u]) {
if (!seen[v]) {
seen[v] = 1;
tree[u].push_back(v);
tree[v].push_back(u);
q.push(v);
}
}
}
const int INF = 1e9;
vector<vector<int>> dist(n, vector<int>(n, INF));
vector<vector<int>> prv(n, vector<int>(n, -1));
for (int s = 0; s < n; s++) {
queue<int> bfs;
dist[s][s] = 0;
prv[s][s] = s;
bfs.push(s);
while (!bfs.empty()) {
int u = bfs.front();
bfs.pop();
for (int v : tree[u]) {
if (dist[s][v] == INF) {
dist[s][v] = dist[s][u] + 1;
prv[s][v] = u;
bfs.push(v);
}
}
}
}
auto treePath = [&](int u, int v) {
vector<int> res;
int x = v;
while (x != u) {
res.push_back(x);
x = prv[u][x];
}
res.push_back(u);
reverse(res.begin(), res.end());
return res;
};
auto pathWithLen = [&](const vector<int>& p, int need) {
if (need == 0) return vector<int>{p[0]};
int d = (int)p.size() - 1;
vector<int> res{p[0]};
int idx = 0;
int twos = d - need;
for (int i = 0; i < twos; i++) {
idx += 2;
res.push_back(p[idx]);
}
while (idx < d) {
idx++;
res.push_back(p[idx]);
}
return res;
};
int A = 0;
for (int i = 0; i < n; i++) if (dist[0][i] > dist[0][A]) A = i;
int B = A;
for (int i = 0; i < n; i++) if (dist[A][i] > dist[A][B]) B = i;
vector<int> diameter = treePath(A, B);
int D = (int)diameter.size() - 1;
int L = (D + 1) / 2;
vector<int> cpos(n), cnode(n);
for (int x = 0; x < n; x++) {
int best = 0;
for (int i = 1; i <= D; i++) {
if (dist[x][diameter[i]] < dist[x][diameter[best]]) best = i;
}
cpos[x] = best;
cnode[x] = diameter[best];
}
function<vector<int>(int,int,int)> makePath = [&](int u, int v, int need) -> vector<int> {
if (need == 0) return {u};
if (dist[u][v] >= need) {
return pathWithLen(treePath(u, v), need);
}
if (cpos[u] != cpos[v]) {
bool rev = false;
if (cpos[u] > cpos[v]) {
swap(u, v);
rev = true;
}
int hu = dist[u][cnode[u]];
int hv = dist[v][cnode[v]];
for (int ru = hu; ru >= (hu + 1) / 2; ru--) {
for (int rv = hv; rv >= (hv + 1) / 2; rv--) {
int rem = need - ru - rv;
if (rem < 1) continue;
auto mid = linePath(D, cpos[u], cpos[v], rem);
if (mid.empty()) continue;
vector<int> left = hu ? pathWithLen(treePath(u, cnode[u]), ru) : vector<int>{u};
vector<int> right = hv ? pathWithLen(treePath(cnode[v], v), rv) : vector<int>{v};
vector<int> res = left;
for (int i = 1; i < (int)mid.size(); i++) res.push_back(diameter[mid[i]]);
for (int i = 1; i < (int)right.size(); i++) res.push_back(right[i]);
if (rev) reverse(res.begin(), res.end());
return res;
}
}
return vector<int>{};
}
int c = cnode[u];
auto isAncestor = [&](int a, int b) {
return dist[c][b] == dist[c][a] + dist[a][b];
};
if (u != v && isAncestor(v, u)) {
auto res = makePath(v, u, need);
reverse(res.begin(), res.end());
return res;
}
if (!isAncestor(u, v)) {
auto up = treePath(u, c);
int p = up[1];
auto rest = makePath(p, v, need - 1);
vector<int> res{u};
res.insert(res.end(), rest.begin(), rest.end());
return res;
}
array<int, 2> ends = {A, B};
for (int e : ends) {
auto line = treePath(e, v);
int s = dist[e][u];
int t = (int)line.size() - 1;
if (s < 0 || s > t || line[s] != u) continue;
auto posPath = linePath(t, s, t, need);
if (posPath.empty()) continue;
vector<int> res;
for (int pos : posPath) res.push_back(line[pos]);
return res;
}
return vector<int>{};
};
vector<vector<int>> op1, op2;
vector<vector<int>> cur = orig;
for (int u = 0; u < n; u++) {
for (int v = u + 1; v < n; v++) {
if (dist[u][v] == 2 && !cur[u][v]) {
int mid = -1;
for (int x = 0; x < n; x++) {
if (dist[u][x] == 1 && dist[v][x] == 1) mid = x;
}
op1.push_back({u, mid, v});
cur[u][v] = cur[v][u] = 1;
}
}
}
for (int u = 0; u < n; u++) {
for (int v = u + 1; v < n; v++) {
if (!cur[u][v]) {
auto p = makePath(u, v, L);
op2.push_back(p);
cur[u][v] = cur[v][u] = 1;
}
}
}
auto printPath = [&](const vector<int>& p) {
for (int i = 0; i < (int)p.size(); i++) {
if (i) cout << ' ';
cout << p[i] + 1;
}
cout << '\n';
};
cout << 2 << '\n';
cout << 3 << '\n';
cout << op1.size() << '\n';
for (auto &p : op1) printPath(p);
cout << L + 1 << '\n';
cout << op2.size() << '\n';
for (auto &p : op2) printPath(p);
}
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
vector<int> directLine(int s, int t, int len) {
if (s == t) return len == 0 ? vector<int>{s} : vector<int>{};
if (s > t) {
auto res = directLine(t, s, len);
reverse(res.begin(), res.end());
return res;
}
int d = t - s;
if ((d + 1) / 2 > len || len > d) return {};
int twos = d - len;
vector<int> res{s};
int cur = s;
for (int i = 0; i < twos; i++) {
cur += 2;
res.push_back(cur);
}
while (cur < t) {
cur++;
res.push_back(cur);
}
return res;
}
vector<int> leftGadget(int p, int waste) {
if (waste == 0) return {p, p + 1};
vector<int> res{p};
if (waste % 2 == 0) {
for (int x = p - 2; x >= p - waste; x -= 2) res.push_back(x);
res.push_back(p - waste + 1);
for (int x = p - waste + 3; x <= p + 1; x += 2) res.push_back(x);
} else {
for (int x = p - 2; x >= p - (waste - 1); x -= 2) res.push_back(x);
res.push_back(p - waste);
for (int x = p - waste + 2; x <= p + 1; x += 2) res.push_back(x);
}
return res;
}
vector<int> rightGadget(int p, int waste) {
if (waste == 0) return {p, p + 1};
vector<int> res{p};
if (waste % 2 == 1) {
for (int x = p + 2; x <= p + waste + 1; x += 2) res.push_back(x);
res.push_back(p + waste);
for (int x = p + waste - 2; x >= p + 1; x -= 2) res.push_back(x);
} else {
for (int x = p + 2; x <= p + waste; x += 2) res.push_back(x);
res.push_back(p + waste + 1);
for (int x = p + waste - 1; x >= p + 1; x -= 2) res.push_back(x);
}
return res;
}
vector<int> linePath(int D, int s, int t, int len) {
if (s == t) return len == 0 ? vector<int>{s} : vector<int>{};
if (s > t) {
auto res = linePath(D, t, s, len);
reverse(res.begin(), res.end());
return res;
}
auto direct = directLine(s, t, len);
if (!direct.empty()) return direct;
int d = t - s;
if (len < d) return {};
int waste = len - d;
if (d == 1) {
if (waste <= s) return leftGadget(s, waste);
if (waste <= D - t) return rightGadget(s, waste);
return {};
}
int low = max(0, waste - (D - t));
int high = min(waste, s);
if (low > high) return {};
int leftWaste = low;
int rightWaste = waste - leftWaste;
vector<int> res = leftGadget(s, leftWaste);
for (int x = s + 2; x < t; x++) res.push_back(x);
auto tail = rightGadget(t - 1, rightWaste);
res.insert(res.end(), tail.begin() + 1, tail.end());
return res;
}
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n, m;
cin >> n >> m;
vector<vector<int>> orig(n, vector<int>(n, 0));
vector<vector<int>> g(n);
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
--u; --v;
orig[u][v] = orig[v][u] = 1;
g[u].push_back(v);
g[v].push_back(u);
}
if (m == n * (n - 1) / 2) {
cout << 0 << '\n';
continue;
}
vector<vector<int>> tree(n);
vector<int> seen(n, 0);
queue<int> q;
seen[0] = 1;
q.push(0);
while (!q.empty()) {
int u = q.front();
q.pop();
for (int v : g[u]) {
if (!seen[v]) {
seen[v] = 1;
tree[u].push_back(v);
tree[v].push_back(u);
q.push(v);
}
}
}
const int INF = 1e9;
vector<vector<int>> dist(n, vector<int>(n, INF));
vector<vector<int>> prv(n, vector<int>(n, -1));
for (int s = 0; s < n; s++) {
queue<int> bfs;
dist[s][s] = 0;
prv[s][s] = s;
bfs.push(s);
while (!bfs.empty()) {
int u = bfs.front();
bfs.pop();
for (int v : tree[u]) {
if (dist[s][v] == INF) {
dist[s][v] = dist[s][u] + 1;
prv[s][v] = u;
bfs.push(v);
}
}
}
}
auto treePath = [&](int u, int v) {
vector<int> res;
int x = v;
while (x != u) {
res.push_back(x);
x = prv[u][x];
}
res.push_back(u);
reverse(res.begin(), res.end());
return res;
};
auto pathWithLen = [&](const vector<int>& p, int need) {
if (need == 0) return vector<int>{p[0]};
int d = (int)p.size() - 1;
vector<int> res{p[0]};
int idx = 0;
int twos = d - need;
for (int i = 0; i < twos; i++) {
idx += 2;
res.push_back(p[idx]);
}
while (idx < d) {
idx++;
res.push_back(p[idx]);
}
return res;
};
int A = 0;
for (int i = 0; i < n; i++) if (dist[0][i] > dist[0][A]) A = i;
int B = A;
for (int i = 0; i < n; i++) if (dist[A][i] > dist[A][B]) B = i;
vector<int> diameter = treePath(A, B);
int D = (int)diameter.size() - 1;
int L = (D + 1) / 2;
vector<int> cpos(n), cnode(n);
for (int x = 0; x < n; x++) {
int best = 0;
for (int i = 1; i <= D; i++) {
if (dist[x][diameter[i]] < dist[x][diameter[best]]) best = i;
}
cpos[x] = best;
cnode[x] = diameter[best];
}
function<vector<int>(int,int,int)> makePath = [&](int u, int v, int need) -> vector<int> {
if (need == 0) return {u};
if (dist[u][v] >= need) {
return pathWithLen(treePath(u, v), need);
}
if (cpos[u] != cpos[v]) {
bool rev = false;
if (cpos[u] > cpos[v]) {
swap(u, v);
rev = true;
}
int hu = dist[u][cnode[u]];
int hv = dist[v][cnode[v]];
for (int ru = hu; ru >= (hu + 1) / 2; ru--) {
for (int rv = hv; rv >= (hv + 1) / 2; rv--) {
int rem = need - ru - rv;
if (rem < 1) continue;
auto mid = linePath(D, cpos[u], cpos[v], rem);
if (mid.empty()) continue;
vector<int> left = hu ? pathWithLen(treePath(u, cnode[u]), ru) : vector<int>{u};
vector<int> right = hv ? pathWithLen(treePath(cnode[v], v), rv) : vector<int>{v};
vector<int> res = left;
for (int i = 1; i < (int)mid.size(); i++) res.push_back(diameter[mid[i]]);
for (int i = 1; i < (int)right.size(); i++) res.push_back(right[i]);
if (rev) reverse(res.begin(), res.end());
return res;
}
}
return vector<int>{};
}
int c = cnode[u];
auto isAncestor = [&](int a, int b) {
return dist[c][b] == dist[c][a] + dist[a][b];
};
if (u != v && isAncestor(v, u)) {
auto res = makePath(v, u, need);
reverse(res.begin(), res.end());
return res;
}
if (!isAncestor(u, v)) {
auto up = treePath(u, c);
int p = up[1];
auto rest = makePath(p, v, need - 1);
vector<int> res{u};
res.insert(res.end(), rest.begin(), rest.end());
return res;
}
array<int, 2> ends = {A, B};
for (int e : ends) {
auto line = treePath(e, v);
int s = dist[e][u];
int t = (int)line.size() - 1;
if (s < 0 || s > t || line[s] != u) continue;
auto posPath = linePath(t, s, t, need);
if (posPath.empty()) continue;
vector<int> res;
for (int pos : posPath) res.push_back(line[pos]);
return res;
}
return vector<int>{};
};
vector<vector<int>> op1, op2;
vector<vector<int>> cur = orig;
for (int u = 0; u < n; u++) {
for (int v = u + 1; v < n; v++) {
if (dist[u][v] == 2 && !cur[u][v]) {
int mid = -1;
for (int x = 0; x < n; x++) {
if (dist[u][x] == 1 && dist[v][x] == 1) mid = x;
}
op1.push_back({u, mid, v});
cur[u][v] = cur[v][u] = 1;
}
}
}
for (int u = 0; u < n; u++) {
for (int v = u + 1; v < n; v++) {
if (!cur[u][v]) {
auto p = makePath(u, v, L);
op2.push_back(p);
cur[u][v] = cur[v][u] = 1;
}
}
}
auto printPath = [&](const vector<int>& p) {
for (int i = 0; i < (int)p.size(); i++) {
if (i) cout << ' ';
cout << p[i] + 1;
}
cout << '\n';
};
cout << 2 << '\n';
cout << 3 << '\n';
cout << op1.size() << '\n';
for (auto &p : op1) printPath(p);
cout << L + 1 << '\n';
cout << op2.size() << '\n';
for (auto &p : op2) printPath(p);
}
}