Every DP is a DAG problem: the states are vertices, the transitions are edges, and the required evaluation order is a topological order. Making that explicit is often the clearest way to think about a DP — and it is the only correct way when the state graph is given as an actual graph.

The equivalence

DP languageGraph language
statevertex
transitionedge
base casesource with a fixed value
evaluation ordertopological order
optimal substructureshortest/longest path
cyclic dependencynot a DAG — needs Dijkstra or Bellman-Ford

The last row is the practical payoff: if you find your DP states depend on each other cyclically, you have not made a mistake in the DP — you have discovered that the problem is a shortest-path problem, and you should switch algorithms.

The template

vector<int> order = topoSort(n, adj);
for (int u : order)
    for (auto [v, w] : adj[u])
        dp[v] = max(dp[v], dp[u] + w);          // or min, or +=, or *=

What it computes

QuestionRecurrence
Longest pathdp[v] = max(dp[u] + w)
Shortest path (negatives fine)dp[v] = min(dp[u] + w)
Number of paths cnt[v] += cnt[u]
Number of shortest pathscarry a count; reset on strict improvement
Longest path ending at each vertexthe DP array itself
Reachability from reverse order, union of successor bitsets
Minimum path coverbipartite matching
Lexicographically smallest longest pathtie-break by vertex label during relaxation

DAGs hiding in plain sight

Most classical DPs are DAG longest-path problems in disguise:

ProblemThe DAG
Grid pathscells; edges right and down
LCS pairs; edges advance one or both indices
LISindices; edge when and
Knapsack
Interval DPintervals; edges to shorter sub-intervals
Bitmask DPsubsets; edges add one element
Course schedulingprerequisites
Build systemsdependencies

Recognising the DAG tells you the complexity immediately: states × transitions.

When the graph has cycles: condense first

Contract strongly connected components to get a DAG, then run the DP on it. Inside an SCC you can move freely, so the component contributes all of its vertices at once. See Condensation Graph.

“Maximum total value collectible on a walk” → condense, weight each component by its internal total, take the longest path.

Longest path is easy here, hard in general

Longest path in a general graph is NP-hard (it contains Hamiltonian path as a special case). On a DAG it is linear. The reason is exactly optimal substructure: with no cycles, an optimal path to extends an optimal path to some predecessor, which is false in a graph with cycles because reusing vertices is forbidden.

Counting paths and the modulus

Path counts explode, so problems ask for the count mod a prime. Two things to watch:

  • Zero-weight cycles are still cycles — check that the graph is genuinely acyclic before counting.
  • Unreachable states should not contribute; initialise carefully (0 for counts, for maxima, and skip unreachable states in the relaxation).

Path reconstruction

Store par[v] on each improving relaxation, then walk back from the best endpoint. For lexicographic requirements, break ties on the parent’s label at relaxation time rather than trying to fix it afterwards.

See also: Shortest Paths on DAGs · Topological Sort · Introduction to DP