DP on DAGs and Graph-Based DP
What This Concept Is
Every correct DP induces a directed acyclic graph (DAG) of subproblems: nodes are subproblem states, edges go from a subproblem to each subproblem it depends on, and the graph is acyclic because each subproblem is strictly smaller in some well-founded order. Filling the DP table in a valid order is equivalent to doing a topological traversal of this DAG.
Making the DAG explicit lets you:
- Solve longest path in a DAG in linear time: process vertices in topological order; $dp[v] = \max_{(u,v) \in E}(dp[u] + w(u,v))$.
- Solve number of paths from $s$ to $t$ in a DAG via the same topological sweep with
+. - Recognize that many DP problems (LIS, edit distance, shortest monotonic paths, scheduling with precedence) are literally longest/shortest paths on implicit DAGs.
- Handle problems with precedence constraints naturally -- topological order is built into the DP.
In the reverse direction, when a DP state is hard to see, reformulating the problem as "find an optimal path on this DAG" sometimes reveals it. This is Erik Demaine's SRTBOT framing in MIT 6.006: every DP = Subproblems + Relate + Topological order + Base cases + Original problem + Time complexity.
More formally: a DP is well-defined iff its subproblem-dependency graph is a DAG. If it has a cycle, the recurrence is self-referential and the computation never terminates. Bellman-Ford and value iteration are DPs over cyclic graphs that converge via iteration to a fixed point -- a separate structural class.
Why It Matters Here
Viewing DP as a DAG computation unifies the module. The fill order is topological order; the recurrence is an edge relaxation; reconstruction is a walk of parent pointers. Problems that feel unrelated -- scheduling, LIS, shortest-path-in-a-grid, task-critical-path -- become instances of the same pattern. This unification is the single biggest payoff of finishing the DP module.
Note that longest path in a general graph is NP-hard, because cycles allow infinite extension (with positive edges) or require cycle-avoidance (NP-hard in itself). Longest path in a DAG is polynomial precisely because of acyclicity.
The DAG viewpoint also clarifies when DP is not the right tool: if the problem's natural subproblem graph has cycles and you cannot iterate to a fixed point (either because the costs are not contractive or because there is no such fixed point), you need Dijkstra, Bellman-Ford, BFS, or another graph algorithm.
Concrete Examples
Example 1: Longest path in a DAG. Given a DAG $G = (V, E)$ with edge weights $w$, compute $dp[v]$ = the length of the longest path ending at $v$.
- Topological-sort $V$.
- Initialize $dp[v] = 0$ for all $v$ (or $-\infty$ if paths must start at a specific source).
- Process vertices in topological order. For each outgoing edge $(v, u)$, update $dp[u] = \max(dp[u], dp[v] + w(v, u))$.
- Answer = $\max_v dp[v]$.
Time $O(V + E)$.
Example 2: LIS as longest path on a DAG. Build a DAG on indices $1..n$ where there is an edge $i \to j$ when $i < j$ and $a[i] < a[j]$, each edge of weight 1. Add a source $0$ with $a[0] = -\infty$ and an edge from $0$ to every vertex. The longest path from $0$ is the LIS length + 1. The $O(n^2)$ LIS DP is literally a topological sweep of this DAG.
Example 3: Number of monotonic paths from $(0,0)$ to $(m-1, n-1)$ in a grid. The grid with right/down moves is a DAG; count paths by the same sweep with $+$.
$$dp[v] = \sum_{(u,v) \in E} dp[u], \quad dp[\text{source}] = 1.$$
Example 4: Critical path method (CPM). Given a task graph with durations and dependencies (a DAG), the critical path -- longest path from start to end -- gives the minimum total time to finish all tasks. This is foundational to project management (PERT, Gantt). It is identical to "longest path on a DAG."
Example 5: Bellman-Ford as DP on iterations. On a general graph with $V$ vertices, $dp[k][v]$ = shortest $s \to v$ path using at most $k$ edges. Recurrence:
$$dp[k][v] = \min\big( dp[k-1][v],\ \min_{(u,v) \in E} dp[k-1][u] + w(u,v) \big).$$
The "layer" index $k$ makes the subproblem DAG acyclic even though the original graph has cycles. This is why Bellman-Ford is a DP despite operating on cyclic graphs.
Example 6: Shortest path in a DAG (single source). Topologically sort vertices; process in order; for each vertex $v$ and each outgoing edge $(v, u)$, relax $d[u] = \min(d[u], d[v] + w(v, u))$. $O(V + E)$ time -- strictly faster than Dijkstra on DAGs, and handles negative edge weights that Dijkstra cannot.
Worked trace -- longest path in a 5-vertex DAG. Edges (with weights): $1\to2:3,\ 1\to3:2,\ 2\to4:4,\ 3\to4:1,\ 4\to5:2$. Topological order: $1,2,3,4,5$. Initialize $dp=[0,0,0,0,0]$. Process $1$: $dp[2]=\max(0,0+3)=3$, $dp[3]=\max(0,0+2)=2$. Process $2$: $dp[4]=\max(0,3+4)=7$. Process $3$: $dp[4]=\max(7,2+1)=7$ (unchanged). Process $4$: $dp[5]=\max(0,7+2)=9$. Process $5$: no outgoing. Answer: longest path length $9$, via $1\to2\to4\to5$. The fill is literally for v in topo: for (v,u) in out[v]: dp[u] = max(dp[u], dp[v] + w).
Common Confusion / Misconceptions
"DP works on any graph." DP-on-DAGs requires acyclicity. On graphs with cycles and nonnegative weights, Dijkstra's algorithm is the correct tool for shortest paths. On graphs with general weights and no negative cycles, Bellman-Ford is. DP-on-DAGs covers a specific, simpler case; do not generalize it beyond DAGs without proof.
"Build the explicit DAG every time." For LIS, you do not materialize $O(n^2)$ edges -- you just loop $j < i$. The DAG view is a mental model for reasoning; the implementation stays compact.
"Topological order is unique." Usually not -- most DAGs have many valid topological orderings. Any one of them suffices for DP. The lexicographically smallest topological order is a specific choice useful for tie-breaks; the DP result is order-independent.
"All DPs fit the DAG framework." Most do. A rare exception: DPs over infinite state spaces (continuous-state MDPs, unbounded integers) need a different framework -- convergence of iterative schemes rather than a finite DAG sweep.
"Longest simple path in a DAG is the same as longest path in a DAG." Yes, because in a DAG all paths are automatically simple (no repeated vertices). This is why DAG longest path is polynomial while general-graph longest simple path is NP-hard.
How To Use It
- Identify the subproblem DAG implicit in a recurrence: nodes are states, edges are dependencies.
- Verify acyclicity (every edge decreases some measure -- index, interval length, subset size, layer).
- Fill the DP in topological order.
- For explicit DAG problems (e.g., longest path, number-of-paths), run the sweep directly.
- If the DAG has cycles or negative weights, switch to the appropriate graph algorithm (Dijkstra, Bellman-Ford, BFS).
- For reconstruction, store
parent[v]alongside $dp[v]$ and walk back from the argmax. - If the state space is large but the actually-reached subset is small (sparse), prefer memoization over topological tabulation.
The measure that guarantees acyclicity is worth naming explicitly. For sequence DPs it is prefix length; for interval DPs it is interval length; for bitmask DPs it is popcount(mask); for tree DPs it is subtree size; for Bellman-Ford it is the layer index (edges used). If you cannot name the measure, you may not have a DAG -- and your recurrence may not terminate. Writing the measure down as a comment next to the recurrence is a cheap sanity gate.
Transfer / Where This Shows Up Later
- S3 Software Design. Build systems (Bazel, Buck) compute minimum rebuild sets via topological DP on target DAGs; artifact cache invalidation is a DAG sweep.
- S5 Networking. BGP best-path selection within an AS is shortest path on a topology DAG-after-loop-prevention; OSPF link-state is Dijkstra, a greedy cousin of DAG DP.
- S6 Databases. Selinger's System R optimizer (1979) uses bottom-up DP over join subsets -- a DAG of subquery plans whose topological order is "by subset size." Modern DPhyp and DPccp optimizers keep this structure.
- S7 Architecture. Architecture decision dependency graphs are DAGs; "compute reachable impact of an ADR change" is a reachability DP on this DAG. Service dependency graphs (microservices) are DAGs; "critical path for a request" is literal longest path.
- Phase 7 AI. Bellman equations $V^(s) = \max_a [R(s,a) + \gamma V^(s')]$ generalize DP-on-DAGs to cyclic state graphs; value iteration is Bellman-Ford with the "layer" being iteration count. Reinforcement learning's credit assignment is DP over the trajectory DAG.
Check Yourself
- Why is longest path in a DAG polynomial, while longest path in a general graph is NP-hard?
- Give a DP you have written and describe its implicit DAG of subproblems.
- What algorithm handles "shortest path" when the graph has cycles and nonnegative weights?
- Why can Bellman-Ford be framed as a DP even though the underlying graph has cycles?
- Topologically sort a DAG with $V = 6$, $E = {(1,2), (1,3), (2,4), (3,4), (4,5), (4,6)}$ and list two valid orderings.
- Express the LIS recurrence as a DAG-longest-path problem.
Mini Drill or Application
- Implement longest path in a DAG given an edge list. Verify on a small hand-crafted example.
- Reformulate LIS as longest path in a DAG and implement the DAG version. Compare its output to a standard LIS on 20 random arrays.
- Count the number of monotonic paths from $(0,0)$ to $(m-1, n-1)$ in a grid with obstacles by topological sweep.
- Given a task graph with durations and dependencies, compute the critical path (longest path in the DAG) -- the minimum time to finish all tasks. Identify which tasks are on the critical path (zero-slack tasks).
- Implement Bellman-Ford as a layered DP and confirm it matches the standard relaxation formulation.
- "Can-reach" DP: given a DAG and a set of target vertices, compute for each vertex whether it can reach at least one target.
- Implement the 5-vertex longest-path trace from the example above; print $dp$ after each vertex is processed and verify it matches.
- Given a DAG of tasks with durations, compute both the earliest start time (forward DP) and the latest start time without delaying the project (backward DP); a task's slack is the difference. Zero-slack tasks form the critical path.
Read This Only If Stuck
If the DAG framing feels abstract: pick any DP you have written (Fibonacci, LIS, edit distance) and draw its dependency graph on paper for $n = 4$ or $m = n = 3$. Label each node with the state tuple. Draw an arrow from each subproblem to the ones it depends on. You will see a DAG. Now draw a second graph with edges reversed. The first is "who do I depend on"; the second is "who depends on me." Topological fill uses the first; push-style updates use the second. Both are valid DP orderings -- they are just two ways of traversing the same DAG.
- Competitive Programming: 9.24 Min Path Cover on DAG
- Competitive Programming: 3.5.3 Non-classical Examples
- Competitive Programming: 3.5.3 Non-classical Examples (Part 2)
- CLRS: 14.3 Elements of dynamic programming (Part 2)
- Skiena: 8.6 Parsing Context-Free Grammars
- Sedgewick: Dynamic Programming chapters
- MIT 6.006 Lec 15: DP Part 1 (DAGs, SRTBOT)
- cp-algorithms: Introduction to DP (DAG framing)
- Wikipedia: Bellman equation
- Jeff Erickson, Algorithms -- Chapter 3: Dynamic Programming (DAG framing, free PDF)
- USACO Guide: Topological Sort -- DAG DP worked examples
- Stanford CS 161: Lecture on DP & DAGs
- Kleinberg & Tardos, Algorithm Design Ch. 6 -- DP on intervals & DAGs
- Erickson, Algorithms Ch. 8 -- Shortest Paths (Bellman-Ford layered DP)
- MIT 6.046J OCW: Dynamic programming on graphs
- Competitive Programmer's Handbook (Laaksonen) Ch. 7 -- Graph algorithms & DP
- cp-algorithms: Topological sort (DAG ordering prerequisite)