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.
Model positions as vertices and restrictions as two-colored undirected edges, with loops allowed. The useful numerical fact is: if , then has the same sign as . Distinct absolute values can therefore make one endpoint completely control each edge.
Imagine choosing the vertex that receives the largest remaining absolute value. It may receive a large positive value only if it has no active type- edge; it may receive a large negative value only if it has no active type- edge.
Repeatedly remove any active vertex whose incident edges omit at least one color. Maintain two active degrees per vertex and a queue. When removing , record sign if its type- degree is zero; otherwise record sign , since its type- degree must be zero.
If the removal order is with recorded signs , set . For every non-loop edge, the endpoint removed first has the larger magnitude, so its recorded sign determines the sum. A loop is handled by the same missing-color rule.
Why is getting stuck a valid NO? In the residual graph every vertex touches both colors. Choose maximizing in a hypothetical solution. If , a type- neighbor must have strictly larger absolute value. If , a type- neighbor has , and then a type- neighbor of must have strictly larger absolute value. Either way, contradiction.
The values themselves are mostly a decoy. What matters is arranging which endpoint dominates each sum.
This may smell like 2-SAT, but signs alone are not enough: for opposite signs, the larger magnitude decides whether the sum is negative. Instead of fighting that fact, we weaponize it.
Create one vertex for every array position. A restriction on becomes an undirected edge, possibly a loop:
The key numerical observation is
Indeed, if , then ; if , then .
So if all absolute values are distinct, every non-loop edge is controlled by the endpoint with larger absolute value. We will use exactly the magnitudes .
Consider only the vertices not handled yet, and suppose we want to give some vertex the largest remaining absolute value . Every other active vertex will eventually have magnitude below , so controls every currently active edge incident to it.
There are only two safe cases:
If both colored degrees are zero, either sign works; we choose negative.
This gives a peeling process: repeatedly remove a vertex whose active incident edges omit at least one color. The first removed vertex receives magnitude , the next receives , and so on.
For each vertex , maintain two active degrees:
Then:
NO.Suppose the resulting removal order is
and the recorded sign of is . Set
Thus earlier removed vertices get strictly larger magnitudes.
A vertex already waiting in the queue is still considered active until it is popped. We keep updating its degrees, then choose its sign using the degrees it has at the actual pop time. This small implementation detail avoids hand-wavy nonsense around two queued adjacent vertices.
Take a non-loop restricted pair , and suppose was removed before . At the moment was removed, was still active, so this edge was counted in the appropriate colored degree of .
Also,
because earlier removal means larger assigned magnitude.
For a loop , its color contributes a positive degree while is active. A type- loop therefore forces the positive-sign rule, giving ; a type- loop forces the negative-sign rule, giving .
Hence every restriction is satisfied.
The nontrivial part is proving that the peeling process does not reject a solvable instance.
Suppose it gets stuck with a nonempty active set . Then every vertex in has at least one active edge of each color. Assume, for contradiction, that some values satisfy all restrictions inside .
Choose maximizing .
Vertex has a type- edge to some , so
Thus , contradicting the choice of . If this edge is a loop, the inequality is already impossible.
Vertex has a type- edge to some , so
Therefore . Maximality forces equality. Now also belongs to the stuck residual graph, so it has a type- edge to some . Consequently,
which gives , again a contradiction. Loops only make the contradiction arrive faster.
So a stuck residual graph cannot have any valid assignment. Therefore the algorithm outputs NO exactly when it should.
Each restriction is stored in both adjacency lists. For a loop, this creates two incidences and increments the degree twice; that is perfectly consistent, since we only care whether a colored degree is zero, and both incidences are removed together.
A boolean array ensures each vertex enters the queue once. Every stored incidence is traversed once, so there is no hidden quadratic disaster lurking in the bushes.
The constructed magnitudes are at most , comfortably below .
For each test case, the time complexity is and the memory complexity is .
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
void solve() {
int n, m;
cin >> n >> m;
vector<array<int, 2>> degree(n, array<int, 2>{0, 0});
vector<vector<pair<int, int>>> graph(n);
while (m--) {
int type, u, v;
cin >> type >> u >> v;
--type;
--u;
--v;
graph[u].push_back({v, type});
graph[v].push_back({u, type});
++degree[u][type];
++degree[v][type];
}
queue<int> q;
vector<char> queued(n, false);
for (int u = 0; u < n; ++u) {
if (degree[u][0] == 0 || degree[u][1] == 0) {
q.push(u);
queued[u] = true;
}
}
vector<pair<int, int>> order;
order.reserve(n);
while (!q.empty()) {
int u = q.front();
q.pop();
int sign = (degree[u][0] == 0 ? -1 : 1);
order.push_back({u, sign});
for (auto [v, type] : graph[u]) {
--degree[v][type];
if (!queued[v] && (degree[v][0] == 0 || degree[v][1] == 0)) {
queued[v] = true;
q.push(v);
}
}
}
if (static_cast<int>(order.size()) != n) {
cout << "NO\n";
return;
}
vector<int> answer(n);
for (int k = 0; k < n; ++k) {
auto [u, sign] = order[k];
answer[u] = sign * (n - k);
}
cout << "YES\n";
for (int i = 0; i < n; ++i) {
cout << answer[i] << (i + 1 == n ? '\n' : ' ');
}
}
int main() {
setIO();
int t;
cin >> t;
while (t--) solve();
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
void solve() {
int n, m;
cin >> n >> m;
vector<array<int, 2>> degree(n, array<int, 2>{0, 0});
vector<vector<pair<int, int>>> graph(n);
while (m--) {
int type, u, v;
cin >> type >> u >> v;
--type;
--u;
--v;
graph[u].push_back({v, type});
graph[v].push_back({u, type});
++degree[u][type];
++degree[v][type];
}
queue<int> q;
vector<char> queued(n, false);
for (int u = 0; u < n; ++u) {
if (degree[u][0] == 0 || degree[u][1] == 0) {
q.push(u);
queued[u] = true;
}
}
vector<pair<int, int>> order;
order.reserve(n);
while (!q.empty()) {
int u = q.front();
q.pop();
int sign = (degree[u][0] == 0 ? -1 : 1);
order.push_back({u, sign});
for (auto [v, type] : graph[u]) {
--degree[v][type];
if (!queued[v] && (degree[v][0] == 0 || degree[v][1] == 0)) {
queued[v] = true;
q.push(v);
}
}
}
if (static_cast<int>(order.size()) != n) {
cout << "NO\n";
return;
}
vector<int> answer(n);
for (int k = 0; k < n; ++k) {
auto [u, sign] = order[k];
answer[u] = sign * (n - k);
}
cout << "YES\n";
for (int i = 0; i < n; ++i) {
cout << answer[i] << (i + 1 == n ? '\n' : ' ');
}
}
int main() {
setIO();
int t;
cin >> t;
while (t--) solve();
}