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.
Do not start by trying to color a scary conflict graph. Two vertices joined by a capacity-1 edge might still be fine together if other same-colored vertices create extra flow. Maxflow is not local like that.
A very strong sufficient condition for a color class is: every cut inside that induced subgraph has even total capacity. Then every pair maxflow is even, so the class is good with .
Every cut in a graph is even if every vertex has even weighted degree. So it is enough to split vertices into two groups such that each vertex has even total capacity to vertices of its own group.
Only edge parities matter for that split. If is the color and is the parity of the weighted degree of , the condition becomes a linear system over : , where is the Laplacian of the odd-capacity-edge graph.
First check whether the whole graph is already good. Use a Gomory-Hu tree: all pair maxflows are represented as path minimums, so the gcd of all pair maxflows is just the gcd of the tree edge weights. If that gcd is not , answer is ; otherwise solve the parity linear system and answer is .
The answer is always either 1 or 2. That is the whole trick. The problem looks like it wants some cursed maxflow-coloring optimization, but the optimization part collapses once you find the parity construction.
We do two things:
Checking one color
For a graph , let
The graph is good exactly when . If every relevant maxflow is , then , and that is still good because every divides .
Computing all maxflows would work on tiny graphs, but there is a cleaner standard tool: a Gomory-Hu tree.
For an undirected capacitated graph, a Gomory-Hu tree is a weighted tree on the same vertices such that for every pair ,
So every pair maxflow is one of the tree edge weights, and every tree edge weight is itself a pair maxflow for its endpoints. Therefore,
Build the Gomory-Hu tree using ordinary maxflow computations. If this gcd is not , output all vertices in one color.
Why two colors always suffice
Now assume the whole graph is not good, so one color is impossible. We will build two colors.
A color class is definitely good if every cut inside its induced subgraph has even capacity. Then every - mincut is even, hence every is even, so divisor works.
A simple way to force all cuts even is to force every vertex degree to be even, where degree means weighted degree inside the induced subgraph.
Why? For any subset of vertices in a graph,
Modulo , the internal part disappears. If all degrees are even, then every cut is even too.
So we only need a 2-coloring such that each vertex has even total capacity to vertices of its own color.
Turning it into linear algebra
Only parity matters now. Let
Let be the color of vertex . Also let
the parity of the odd-edge degree of .
For vertex , the parity of its same-color degree is
Over ,
So the condition becomes
Expanding:
or
This is a linear system over , where is the Laplacian of the graph formed by odd-capacity edges. In mod , plus and minus are the same, so the off-diagonal entries are just adjacency parities, and the diagonal is .
Why the system is always solvable
This is the bit that makes the construction not just wishful thinking.
Take any vector with . Since is symmetric,
But over , all off-diagonal terms cancel in pairs, so
Thus every vector in the nullspace of is orthogonal to . For a symmetric matrix, the column space is exactly the orthogonal complement of the nullspace, so is in the column space of . Therefore always has a solution.
In code, we do not need to be fancy: just run Gaussian elimination over bits.
Why this is optimal
If the Gomory-Hu gcd test says the graph is good, one color is clearly minimum.
If it says the gcd is , the whole graph is not good, so one color is impossible. The parity system gives a valid two-coloring. Therefore the minimum is exactly .
That is it. The maxflow part only decides whether we can stop at one color; the coloring itself is pure parity algebra.
Complexity
The Gomory-Hu tree uses maxflow computations. Here , so Dinic is easily fine. The Gaussian elimination is with bitsets, basically nothing.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct InputEdge {
int u, v;
ll w;
};
struct Dinic {
struct Edge {
int to, rev;
ll cap;
};
int n;
vector<vector<Edge>> g;
vector<int> level, it;
Dinic(int n) : n(n), g(n), level(n), it(n) {}
void addEdge(int v, int to, ll cap) {
Edge a{to, (int)g[to].size(), cap};
Edge b{v, (int)g[v].size(), 0};
g[v].push_back(a);
g[to].push_back(b);
}
bool bfs(int s, int t) {
fill(level.begin(), level.end(), -1);
queue<int> q;
level[s] = 0;
q.push(s);
while (!q.empty()) {
int v = q.front();
q.pop();
for (const auto &e : g[v]) {
if (e.cap > 0 && level[e.to] == -1) {
level[e.to] = level[v] + 1;
q.push(e.to);
}
}
}
return level[t] != -1;
}
ll dfs(int v, int t, ll pushed) {
if (v == t) return pushed;
for (int &i = it[v]; i < (int)g[v].size(); i++) {
Edge &e = g[v][i];
if (e.cap <= 0 || level[e.to] != level[v] + 1) continue;
ll tr = dfs(e.to, t, min(pushed, e.cap));
if (!tr) continue;
e.cap -= tr;
g[e.to][e.rev].cap += tr;
return tr;
}
return 0;
}
ll maxflow(int s, int t) {
ll flow = 0;
const ll INF = (ll)4e18;
while (bfs(s, t)) {
fill(it.begin(), it.end(), 0);
while (true) {
ll pushed = dfs(s, t, INF);
if (!pushed) break;
flow += pushed;
}
}
return flow;
}
vector<int> reachable(int s) const {
vector<int> seen(n, 0);
queue<int> q;
seen[s] = 1;
q.push(s);
while (!q.empty()) {
int v = q.front();
q.pop();
for (const auto &e : g[v]) {
if (e.cap > 0 && !seen[e.to]) {
seen[e.to] = 1;
q.push(e.to);
}
}
}
return seen;
}
};
pair<ll, vector<int>> mincut(int n, const vector<InputEdge> &edges, int s, int t) {
Dinic dinic(n);
for (auto e : edges) {
dinic.addEdge(e.u, e.v, e.w);
dinic.addEdge(e.v, e.u, e.w);
}
ll f = dinic.maxflow(s, t);
return {f, dinic.reachable(s)};
}
ll gomoryHuGcd(int n, const vector<InputEdge> &edges) {
if (n <= 1) return 0;
vector<int> parent(n, 0);
vector<ll> weight(n, 0);
for (int s = 1; s < n; s++) {
int t = parent[s];
auto [f, cut] = mincut(n, edges, s, t);
for (int i = s + 1; i < n; i++) {
if (parent[i] == t && cut[i]) parent[i] = s;
}
if (cut[parent[t]]) {
parent[s] = parent[t];
parent[t] = s;
weight[s] = weight[t];
weight[t] = f;
} else {
weight[s] = f;
}
}
ll g = 0;
for (int i = 1; i < n; i++) g = std::gcd(g, weight[i]);
return g;
}
vector<int> parityColoring(int n, const vector<InputEdge> &edges) {
static const int B = 64;
vector<bitset<B>> a(n);
for (auto e : edges) {
if ((e.w & 1) == 0) continue;
int u = e.u, v = e.v;
a[u].flip(v);
a[v].flip(u);
a[u].flip(u);
a[v].flip(v);
a[u].flip(n);
a[v].flip(n);
}
vector<int> where(n, -1);
int row = 0;
for (int col = 0; col < n; col++) {
int sel = -1;
for (int i = row; i < n; i++) {
if (a[i][col]) {
sel = i;
break;
}
}
if (sel == -1) continue;
swap(a[sel], a[row]);
where[col] = row;
for (int i = 0; i < n; i++) {
if (i != row && a[i][col]) a[i] ^= a[row];
}
row++;
}
vector<int> x(n, 0);
for (int col = 0; col < n; col++) {
if (where[col] != -1) x[col] = (int)a[where[col]][n];
}
return x;
}
void printGroup(const vector<int> &v) {
cout << v.size() << '\n';
for (int i = 0; i < (int)v.size(); i++) {
if (i) cout << ' ';
cout << v[i] + 1;
}
cout << '\n';
}
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n, m;
cin >> n >> m;
vector<InputEdge> edges(m);
for (auto &e : edges) {
cin >> e.u >> e.v >> e.w;
--e.u;
--e.v;
}
ll g = gomoryHuGcd(n, edges);
if (g != 1) {
vector<int> all(n);
iota(all.begin(), all.end(), 0);
cout << 1 << '\n';
printGroup(all);
} else {
vector<int> color = parityColoring(n, edges);
vector<int> zero, one;
for (int i = 0; i < n; i++) {
if (color[i]) one.push_back(i);
else zero.push_back(i);
}
cout << 2 << '\n';
printGroup(zero);
printGroup(one);
}
}
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct InputEdge {
int u, v;
ll w;
};
struct Dinic {
struct Edge {
int to, rev;
ll cap;
};
int n;
vector<vector<Edge>> g;
vector<int> level, it;
Dinic(int n) : n(n), g(n), level(n), it(n) {}
void addEdge(int v, int to, ll cap) {
Edge a{to, (int)g[to].size(), cap};
Edge b{v, (int)g[v].size(), 0};
g[v].push_back(a);
g[to].push_back(b);
}
bool bfs(int s, int t) {
fill(level.begin(), level.end(), -1);
queue<int> q;
level[s] = 0;
q.push(s);
while (!q.empty()) {
int v = q.front();
q.pop();
for (const auto &e : g[v]) {
if (e.cap > 0 && level[e.to] == -1) {
level[e.to] = level[v] + 1;
q.push(e.to);
}
}
}
return level[t] != -1;
}
ll dfs(int v, int t, ll pushed) {
if (v == t) return pushed;
for (int &i = it[v]; i < (int)g[v].size(); i++) {
Edge &e = g[v][i];
if (e.cap <= 0 || level[e.to] != level[v] + 1) continue;
ll tr = dfs(e.to, t, min(pushed, e.cap));
if (!tr) continue;
e.cap -= tr;
g[e.to][e.rev].cap += tr;
return tr;
}
return 0;
}
ll maxflow(int s, int t) {
ll flow = 0;
const ll INF = (ll)4e18;
while (bfs(s, t)) {
fill(it.begin(), it.end(), 0);
while (true) {
ll pushed = dfs(s, t, INF);
if (!pushed) break;
flow += pushed;
}
}
return flow;
}
vector<int> reachable(int s) const {
vector<int> seen(n, 0);
queue<int> q;
seen[s] = 1;
q.push(s);
while (!q.empty()) {
int v = q.front();
q.pop();
for (const auto &e : g[v]) {
if (e.cap > 0 && !seen[e.to]) {
seen[e.to] = 1;
q.push(e.to);
}
}
}
return seen;
}
};
pair<ll, vector<int>> mincut(int n, const vector<InputEdge> &edges, int s, int t) {
Dinic dinic(n);
for (auto e : edges) {
dinic.addEdge(e.u, e.v, e.w);
dinic.addEdge(e.v, e.u, e.w);
}
ll f = dinic.maxflow(s, t);
return {f, dinic.reachable(s)};
}
ll gomoryHuGcd(int n, const vector<InputEdge> &edges) {
if (n <= 1) return 0;
vector<int> parent(n, 0);
vector<ll> weight(n, 0);
for (int s = 1; s < n; s++) {
int t = parent[s];
auto [f, cut] = mincut(n, edges, s, t);
for (int i = s + 1; i < n; i++) {
if (parent[i] == t && cut[i]) parent[i] = s;
}
if (cut[parent[t]]) {
parent[s] = parent[t];
parent[t] = s;
weight[s] = weight[t];
weight[t] = f;
} else {
weight[s] = f;
}
}
ll g = 0;
for (int i = 1; i < n; i++) g = std::gcd(g, weight[i]);
return g;
}
vector<int> parityColoring(int n, const vector<InputEdge> &edges) {
static const int B = 64;
vector<bitset<B>> a(n);
for (auto e : edges) {
if ((e.w & 1) == 0) continue;
int u = e.u, v = e.v;
a[u].flip(v);
a[v].flip(u);
a[u].flip(u);
a[v].flip(v);
a[u].flip(n);
a[v].flip(n);
}
vector<int> where(n, -1);
int row = 0;
for (int col = 0; col < n; col++) {
int sel = -1;
for (int i = row; i < n; i++) {
if (a[i][col]) {
sel = i;
break;
}
}
if (sel == -1) continue;
swap(a[sel], a[row]);
where[col] = row;
for (int i = 0; i < n; i++) {
if (i != row && a[i][col]) a[i] ^= a[row];
}
row++;
}
vector<int> x(n, 0);
for (int col = 0; col < n; col++) {
if (where[col] != -1) x[col] = (int)a[where[col]][n];
}
return x;
}
void printGroup(const vector<int> &v) {
cout << v.size() << '\n';
for (int i = 0; i < (int)v.size(); i++) {
if (i) cout << ' ';
cout << v[i] + 1;
}
cout << '\n';
}
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n, m;
cin >> n >> m;
vector<InputEdge> edges(m);
for (auto &e : edges) {
cin >> e.u >> e.v >> e.w;
--e.u;
--e.v;
}
ll g = gomoryHuGcd(n, edges);
if (g != 1) {
vector<int> all(n);
iota(all.begin(), all.end(), 0);
cout << 1 << '\n';
printGroup(all);
} else {
vector<int> color = parityColoring(n, edges);
vector<int> zero, one;
for (int i = 0; i < n; i++) {
if (color[i]) one.push_back(i);
else zero.push_back(i);
}
cout << 2 << '\n';
printGroup(zero);
printGroup(one);
}
}
}