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.
Look at the prefix of labels . Which vertices can have an edge into this prefix? That gives a very nasty necessary condition immediately.
Let . For a Hamiltonian cycle, you obviously need . But for , equality is also bad. Why? Think about what happens to all outgoing edges of those vertices.
Instead of choosing the successor of every vertex directly, try choosing the predecessor of each label . At step , any predecessor must be a vertex with that has not been used as a predecessor before.
If you add edges in increasing order of destination , you can maintain that the chosen edges form a disjoint union of directed paths. Then the only safe vertices to use as predecessors are the current path tails. Also, vertex itself is the head of its path, because nobody has pointed to yet.
After processing destinations , the number of available path tails is exactly . If for every , then before each non-final step there are at least available tails, so one of them lies outside the current component of . Connect that tail to and union the components. After such merges you have one big path, and the last remaining tail connects to to close the Hamiltonian cycle.
The graph looks scary, but every row is just a suffix. That kills most of the complexity dead.
A directed edge exists iff .
So for a fixed , the only vertices that can point into the prefix are exactly the vertices with .
Let
Now suppose a Hamiltonian cycle exists.
Therefore the necessary condition is actually
That is also sufficient. Yep, that simple. The rest is just construction.
If you sort increasingly into , this condition is equivalent to
Very parking-function-ish. Cute, but we still need the actual cycle.
Instead of deciding the cycle order directly, decide for each vertex who points to it.
Process destinations . At step , we want to choose some vertex and add the edge
What must be true?
This screams: keep the chosen edges as a bunch of disjoint directed paths.
Initially, every vertex is an isolated path.
Suppose after processing , all chosen edges form disjoint directed paths. Then:
So each current component contributes at most one usable predecessor.
Also, vertex has not been processed as a destination yet, so nobody points to yet. Thus has indegree , which means it is the head of its current path.
Therefore, if we connect a tail from a different component to ,
we simply glue two paths into one larger path. No cycle appears. Nice and clean.
At step , all vertices with are already allowed to be predecessors. There are exactly such vertices.
Among them, exactly have already been used as predecessors in earlier steps, so the number of currently available predecessors is
Now for :
So there are at least available vertices. Since each component contributes at most one available tail, at most one available vertex can lie in the same component as . Hence there is always some available tail in a different component. Use it.
This merges two components, so after steps we have merged components exactly times:
So right before step , there is exactly one path containing all vertices.
At step , the number of available predecessors is
That one remaining available vertex is the tail of the big path. Vertex is its head (nobody pointed to earlier). Connecting tail to head closes the path into one Hamiltonian cycle.
Done. No black magic, just a stubborn invariant.
We need two things:
A vertex becomes available exactly when we reach . So we bucket vertices by value and add bucket when processing destination .
A super handy fact: each component has at most one available vertex. So if we store available vertices in a simple vector, then:
That gives an construction per test case.
So the algorithm outputs No iff impossible, otherwise outputs a Hamiltonian cycle.
For each test case:
Total over all test cases:
which is easily fine for .
Pretty elegant, honestly. The graph tries to look intimidating, but itβs mostly just suffixes and bookkeeping.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct DSU {
vector<int> p, sz;
DSU(int n = 0) { init(n); }
void init(int n) {
p.resize(n + 1);
sz.assign(n + 1, 1);
iota(p.begin(), p.end(), 0);
}
int find(int x) {
while (p[x] != x) {
p[x] = p[p[x]];
x = p[x];
}
return x;
}
bool unite(int a, int b) {
a = find(a);
b = find(b);
if (a == b) return false;
if (sz[a] < sz[b]) swap(a, b);
p[b] = a;
sz[a] += sz[b];
return true;
}
};
int main() { setIO();
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<int> a(n + 1);
vector<vector<int>> bucket(n + 1);
for (int i = 1; i <= n; ++i) {
cin >> a[i];
bucket[a[i]].push_back(i);
}
bool ok = true;
int pref = 0;
for (int k = 1; k < n; ++k) {
pref += (int)bucket[k].size();
if (pref <= k) {
ok = false;
break;
}
}
if (!ok) {
cout << "No\n";
continue;
}
DSU dsu(n);
vector<int> active;
active.reserve(n);
vector<int> nxt(n + 1, 0);
for (int j = 1; j <= n; ++j) {
for (int v : bucket[j]) active.push_back(v);
int idx = (int)active.size() - 1;
if (j < n && dsu.find(active[idx]) == dsu.find(j)) {
--idx;
}
int u = active[idx];
active[idx] = active.back();
active.pop_back();
nxt[u] = j;
dsu.unite(u, j);
}
vector<int> ans;
ans.reserve(n);
int cur = 1;
for (int i = 0; i < n; ++i) {
ans.push_back(cur);
cur = nxt[cur];
}
cout << "Yes\n";
for (int i = 0; i < n; ++i) {
if (i) cout << ' ';
cout << ans[i];
}
cout << '\n';
}
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct DSU {
vector<int> p, sz;
DSU(int n = 0) { init(n); }
void init(int n) {
p.resize(n + 1);
sz.assign(n + 1, 1);
iota(p.begin(), p.end(), 0);
}
int find(int x) {
while (p[x] != x) {
p[x] = p[p[x]];
x = p[x];
}
return x;
}
bool unite(int a, int b) {
a = find(a);
b = find(b);
if (a == b) return false;
if (sz[a] < sz[b]) swap(a, b);
p[b] = a;
sz[a] += sz[b];
return true;
}
};
int main() { setIO();
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<int> a(n + 1);
vector<vector<int>> bucket(n + 1);
for (int i = 1; i <= n; ++i) {
cin >> a[i];
bucket[a[i]].push_back(i);
}
bool ok = true;
int pref = 0;
for (int k = 1; k < n; ++k) {
pref += (int)bucket[k].size();
if (pref <= k) {
ok = false;
break;
}
}
if (!ok) {
cout << "No\n";
continue;
}
DSU dsu(n);
vector<int> active;
active.reserve(n);
vector<int> nxt(n + 1, 0);
for (int j = 1; j <= n; ++j) {
for (int v : bucket[j]) active.push_back(v);
int idx = (int)active.size() - 1;
if (j < n && dsu.find(active[idx]) == dsu.find(j)) {
--idx;
}
int u = active[idx];
active[idx] = active.back();
active.pop_back();
nxt[u] = j;
dsu.unite(u, j);
}
vector<int> ans;
ans.reserve(n);
int cur = 1;
for (int i = 0; i < n; ++i) {
ans.push_back(cur);
cur = nxt[cur];
}
cout << "Yes\n";
for (int i = 0; i < n; ++i) {
if (i) cout << ' ';
cout << ans[i];
}
cout << '\n';
}
}