Problems that reduce to shortest paths after a construction: systems of difference constraints, graphs with range edges (segment-tree graphs), XOR-weighted walks (cycle basis), shortest cycles, minimum mean / ratio cycles, and replacement paths (distance when one edge is removed). Shortest paths modulo the smallest coin are in Knapsack.
Difference constraints
is an edge of weight . With a virtual source joined to all vertices by weight-0 edges, the Bellman–Ford distances are a solution; a negative cycle means infeasible. The solution found is the componentwise maximum among solutions with all ; for the minimum solution with solve for . Over integers, is ; is .
optional<vector<ll>> diffConstraints(int n, const vector<array<ll, 3>>& E) { // {u, v, c}: x_v - x_u <= c
vector<ll> x(n, 0);
for (int it = 0; it <= n; it++) {
bool ch = false;
for (auto [u, v, c] : E) if (x[u] + c < x[v]) x[v] = x[u] + c, ch = true;
if (!ch) return x;
}
return nullopt;
}Proof: feasibility iff no negative cycle, and maximality
Summing the constraints around a cycle gives , so a negative cycle makes the system infeasible. Without one, shortest distances from the virtual source exist and satisfy for every edge, i.e. every constraint, with . For any solution with , induction along a shortest path (starting from ) gives , so is the componentwise maximum.
Typical: scheduling with “at least / at most apart”, prefix-sum constraints on 0/1 arrays ( plus ), and verifying consistency of relative measurements. If all constraints have the form with only, Dijkstra applies.
Range edges: segment-tree graph
Edges “vertex every vertex in ”, “every vertex in vertex”, or range range: build two segment trees of helper nodes, an out-tree with weight-0 edges parent children (reaching a node reaches all its leaves) and an in-tree with weight-0 edges children parent. Each range is nodes; range range uses one extra hub node. Dijkstra on edges (CF 786B).
struct RangeGraph {
int n, S = 1; vector<vector<pair<int, ll>>> adj;
int outNode(int k) const { return k >= S ? k - S : n + k; } // leaves are the real vertices
int inNode(int k) const { return k >= S ? k - S : n + S + k; }
RangeGraph(int n) : n(n) {
while (S < n) S <<= 1;
adj.assign(n + 2 * S, {});
for (int k = 1; k < S; k++) for (int c : {2 * k, 2 * k + 1}) {
if (c >= S && c - S >= n) continue;
adj[outNode(k)].push_back({outNode(c), 0}); adj[inNode(c)].push_back({inNode(k), 0});
}
}
vector<int> cover(int l, int r, bool out) const {
vector<int> res;
for (l += S, r += S; l < r; l >>= 1, r >>= 1) {
if (l & 1) res.push_back(out ? outNode(l) : inNode(l)), l++;
if (r & 1) --r, res.push_back(out ? outNode(r) : inNode(r));
}
return res;
}
void edge(int u, int v, ll w) { adj[u].push_back({v, w}); }
void toRange(int u, int l, int r, ll w) { for (int x : cover(l, r, true)) adj[u].push_back({x, w}); }
void fromRange(int l, int r, int v, ll w) { for (int x : cover(l, r, false)) adj[x].push_back({v, w}); }
void rangeToRange(int l1, int r1, int l2, int r2, ll w) {
int hub = sz(adj); adj.push_back({});
for (int x : cover(l1, r1, false)) adj[x].push_back({hub, w});
for (int y : cover(l2, r2, true)) adj[hub].push_back({y, 0});
}
}; // run Dijkstra on adj; the first n entries are the real verticesOther virtual-node patterns: one node per colour/value (enter with the cost, leave free), one node per divisor for “jump between multiples”, and a node per row/column for “teleport within a row”.
XOR of walks
For an undirected graph with XOR edge weights, the XOR of any walk from to equals (along a spanning tree) XOR some combination of fundamental cycle values, and every combination is achievable. So the maximum (or minimum) XOR walk is reduced greedily against a linear basis of the cycle values (CF 845G).
struct XorWalks {
vector<ull> dist; vector<int> comp; vector<array<ull, 64>> basis;
static void insert(array<ull, 64>& b, ull x) { for (int i = 63; i >= 0 && x; i--) if (x >> i & 1) { if (!b[i]) { b[i] = x; return; } x ^= b[i]; } }
XorWalks(int n, const vector<array<ull, 3>>& E) : dist(n), comp(n, -1) {
vector<vector<pair<int, ull>>> g(n);
for (auto [u, v, w] : E) g[u].push_back({(int)v, w}), g[v].push_back({(int)u, w});
for (int s = 0; s < n; s++) {
if (comp[s] >= 0) continue;
int id = sz(basis); basis.push_back({}); comp[s] = id; vector<int> st{s};
while (sz(st)) {
int u = st.back(); st.pop_back();
for (auto [v, w] : g[u]) {
if (comp[v] < 0) comp[v] = id, dist[v] = dist[u] ^ w, st.push_back(v);
else insert(basis[id], dist[u] ^ dist[v] ^ w);
}
}
}
}
ull maxXor(int u, int v) const { ull x = dist[u] ^ dist[v]; for (int i = 63; i >= 0; i--) x = max(x, x ^ basis[comp[u]][i]); return x; }
ull minXor(int u, int v) const { ull x = dist[u] ^ dist[v]; for (int i = 63; i >= 0; i--) x = min(x, x ^ basis[comp[u]][i]); return x; }
};Proof: walk XORs are tree path XOR plus the cycle space
Any walk’s XOR is the XOR of the edges used an odd number of times; that edge set minus the tree path is an even-degree subgraph, hence a combination of fundamental cycles (Cycles), whose XOR value is the XOR of the cycles’ values. Conversely any cycle in the component can be added to a walk: go from the walk to , around it, and back along the same path (the connecting path cancels). The cycle value of non-tree edge is . Greedy reduction from the top bit maximises (minimises) the XOR over the span.
Shortest cycles (girth)
- Unweighted undirected: BFS from every vertex; a non-tree edge gives a closed walk of length through the root, and the minimum over all roots is the girth. . (The walk through a root might not be simple for that root, but the overall minimum is attained by a simple cycle when the root lies on it.)
- Weighted undirected, through a vertex : Dijkstra from , label every vertex by the first edge of its shortest path (“branch”); a non-tree edge joining different branches (or touching ) gives a cycle through of length .
- Through an edge : + shortest – path avoiding that edge.
- Directed: Dijkstra from each ; the shortest cycle through is . ; with unit weights BFS, .
ll girthUnweighted(const vector<vector<int>>& g) { // undirected, unweighted; LLONG_MAX if acyclic
int n = sz(g); ll best = LLONG_MAX;
for (int s = 0; s < n; s++) {
vector<int> d(n, -1), p(n, -1); queue<int> q; d[s] = 0; q.push(s);
while (sz(q)) {
int u = q.front(); q.pop();
for (int v : g[u]) {
if (d[v] < 0) d[v] = d[u] + 1, p[v] = u, q.push(v);
else if (p[u] != v) best = min(best, (ll)d[u] + d[v] + 1); // simple graph assumed
}
}
}
return best;
}Proof: the BFS girth bound is exact
Every value recorded is the length of a closed walk containing a cycle, so it is the girth . For a shortest cycle and , BFS from gives every vertex of distance (along ), and the cycle contains an edge that is not a BFS tree edge with (the edge “opposite” on ). Since every recorded value is , the minimum equals .
Minimum mean cycle (Karp)
. With = minimum weight of a walk with exactly edges ending at (starting anywhere, ):
. For minimum ratio cycles (), binary search and test for a negative cycle with weights (also how min mean cycle is often done).
pair<ll, ll> minMeanCycle(int n, const vector<array<ll, 3>>& E) { // {num, den}; den == 0 if acyclic
const ll INF = LLONG_MAX / 4;
vector<vector<ll>> d(n + 1, vector<ll>(n, INF)); d[0].assign(n, 0);
for (int k = 1; k <= n; k++) for (auto [u, v, w] : E) if (d[k - 1][u] < INF) d[k][v] = min(d[k][v], d[k - 1][u] + w);
ll bn = 0, bd = 0; // best = bn / bd
for (int v = 0; v < n; v++) {
if (d[n][v] >= INF) continue;
ll wn = 0, wd = 0; // max over k
for (int k = 0; k < n; k++) if (d[k][v] < INF) {
ll num = d[n][v] - d[k][v], den = n - k;
if (!wd || (__int128)num * wd > (__int128)wn * den) wn = num, wd = den;
}
if (wd && (!bd || (__int128)wn * bd < (__int128)bn * wd)) bn = wn, bd = wd;
}
return {bn, bd};
}Proof: Karp's formula
Subtracting from every weight subtracts from both sides, so assume : no negative cycles, and some cycle has weight 0. Let , the shortest walk weight to (shortest walks can be taken with edges). A walk of edges contains a cycle of weight , so , hence for every : the right side is . For equality, take any and a shortest walk to with edges, and extend it along by edges to a vertex . The triangle inequality gives and (arcs of ), and , so : so this -edge walk has weight : , and then every term with equality at the attaining . So the minimum over is exactly 0.
Where it shows up: repeatedly finding and cancelling minimum mean cycles is a strongly-polynomial min-cost flow algorithm; negating weights gives the maximum mean cycle; minimum mean cycle on a log-transformed exchange-rate graph finds the best arbitrage rate (not just whether one exists, unlike the plain negative-cycle check); and min/max mean cycle bounds are the standard tool in digital-circuit and pipeline timing verification. The related Karp–Orlin algorithm reuses the same mean-weight machinery for parametric shortest paths.
Replacement paths
Undirected, positive weights: the – distance after deleting each edge, in . Only edges of one fixed shortest path matter. For each vertex let = index of the last path edge on the shortest-path-tree route from to that leaves as late as possible, and symmetric from . A non-path edge offers detour for all path edges with index in : a sweep with a multiset gives every answer. Changing edge ‘s weight to (CF 1163F): .
struct ReplacementPaths { // edges (u, v, w), w > 0; t reachable from s
const ll INF = LLONG_MAX / 4; vector<ll> ds, dt, best; vector<int> path, pid; int t;
vector<ll> dijk(int n, const vector<vector<array<int, 2>>>& g, const vector<array<ll, 3>>& E, int src, vector<int>* pe) {
vector<ll> d(n, INF); priority_queue<pair<ll, int>, vector<pair<ll, int>>, greater<>> pq; d[src] = 0; pq.push({0, src});
while (sz(pq)) { auto [du, u] = pq.top(); pq.pop(); if (du != d[u]) continue;
for (auto [v, e] : g[u]) if (du + E[e][2] < d[v]) { d[v] = du + E[e][2]; if (pe) (*pe)[v] = e; pq.push({d[v], v}); } }
return d;
}
ReplacementPaths(int n, const vector<array<ll, 3>>& E, int s, int t) : t(t) {
int m = sz(E); vector<vector<array<int, 2>>> g(n);
for (int e = 0; e < m; e++) g[E[e][0]].push_back({(int)E[e][1], e}), g[E[e][1]].push_back({(int)E[e][0], e});
vector<int> pe(n, -1); ds = dijk(n, g, E, s, &pe); dt = dijk(n, g, E, t, nullptr);
pid.assign(m, -1);
for (int c = t; c != s;) { int e = pe[c]; path.push_back(e); c ^= E[e][0] ^ E[e][1]; }
reverse(all(path)); int k = sz(path);
for (int i = 0; i < k; i++) pid[path[i]] = i;
vector<int> ord(n), l(n, k), r(n, -1); iota(all(ord), 0);
sort(all(ord), [&](int a, int b) { return ds[a] < ds[b]; }); l[s] = -1;
for (int u : ord) if (ds[u] < INF) for (auto [v, e] : g[u]) if (ds[u] + E[e][2] == ds[v]) l[v] = min(l[v], pid[e] >= 0 ? pid[e] : l[u]);
sort(all(ord), [&](int a, int b) { return dt[a] < dt[b]; }); r[t] = k;
for (int u : ord) if (dt[u] < INF) for (auto [v, e] : g[u]) if (dt[u] + E[e][2] == dt[v]) r[v] = max(r[v], pid[e] >= 0 ? pid[e] : r[u]);
vector<vector<pair<ll, int>>> ev(k + 1);
for (int e = 0; e < m; e++) if (pid[e] < 0) for (auto [u, v] : {pair{(int)E[e][0], (int)E[e][1]}, pair{(int)E[e][1], (int)E[e][0]}}) {
if (ds[u] >= INF || dt[v] >= INF || l[u] + 1 > r[v] - 1) continue;
ll c = ds[u] + E[e][2] + dt[v]; ev[l[u] + 1].push_back({c, 1}); ev[r[v]].push_back({c, -1});
}
best.assign(k, INF); multiset<ll> act;
for (int i = 0; i < k; i++) {
for (auto [c, a] : ev[i]) { if (a > 0) act.insert(c); else act.erase(act.find(c)); }
if (sz(act)) best[i] = *act.begin();
}
}
ll without(int e) const { return pid[e] < 0 ? ds[t] : best[pid[e]]; }
};Proof sketch: the detour intervals
After deleting path edge , an optimal replacement path leaves ‘s prefix structure at some point and rejoins later; it can be chosen to consist of a shortest – path, one non-path edge , and a shortest – path (a standard exchange: any optimal replacement path contains such an edge crossing from the ” side” to the ” side” of the cut defined by ). The shortest – route avoids exactly when it leaves before , i.e. , where taking the route that departs as late as possible makes well defined via the shortest-path DAG; symmetrically the – route avoids iff . So edge serves exactly the indices , and the sweep takes the minimum over active candidates.