Every DP is a computation over a DAG of states. When the states form an actual graph, process them in topological order (or condense SCCs first). Probability DPs push mass forward from the start; expectation DPs pull backward from terminal states. Self-loops are solved algebraically, general cycles by Gaussian elimination, and cycles with special structure (a line, a tree) by expressing each value as a linear function of one neighbour. The probability theory itself is in Probability and Expectation.
DP on a DAG
| Question | Recurrence in topological order |
|---|---|
| longest / shortest path (any weights) | dp[v] = max/min(dp[u] + w) |
| number of paths | cnt[v] += cnt[u] |
| number of shortest paths | carry (dist, count), reset the count on strict improvement |
| reachability for all pairs | reverse order, `reach[u] |
| lexicographically smallest optimal path | build backwards so the choice at each vertex is local |
| cyclic graph, value collected per vertex | condense SCCs, weight each by its total (SCC) |
Longest path is NP-hard in general graphs and linear on DAGs: with no cycles, the best path to extends the best path to some predecessor.
Most classical DPs are exactly this, in disguise — recognising the DAG gives the complexity immediately (states × transitions):
| Problem | The DAG |
|---|---|
| grid paths | cells; edges right and down |
| LCS | pairs; edges advance one or both indices |
| LIS | indices; edge when and |
| knapsack | (item, capacity) |
| interval DP | intervals; edges to shorter sub-intervals |
| bitmask DP | subsets; edges add one element |
| course scheduling / build systems | prerequisites / dependencies |
Two pitfalls specific to path-counting: a zero-weight cycle is still a cycle — verify the graph is genuinely acyclic before trusting a path count; and unreachable states must not contribute (initialise to 0 for counts, for maxima, and skip them during relaxation rather than letting stale values leak in). For lexicographically-smallest reconstruction, break ties on the parent’s label at relaxation time, not by post-processing afterwards.
Probability and expectation DPs
- Forward: ; push in topological order. Answers “probability of ending in state ”.
- Backward: , terminal states 0. Answers “expected cost/time from ”.
- Linearity first: always; many “expected count” problems are a sum of per-item probabilities and need no DP. E.g. expected fixed points of a random permutation: each element is fixed with probability , so the answer is , with no DP at all.
- Randomness plus optimal play: when both players play optimally and some transitions are random, it’s game DP with an expectation taken at the chance nodes, not a pure expectation DP.
- or of integer variables: .
- Modular answers: represent as ; everything above stays exact.
// Bad Luck Island (r rocks, s scissors, p papers meet uniformly at random): survival probabilities, forward DP
array<double, 3> island(int R, int S, int P) {
vector dp(R + 1, vector(S + 1, vector<double>(P + 1)));
array<double, 3> ans{}; dp[R][S][P] = 1;
for (int r = R; r >= 0; r--) for (int s = S; s >= 0; s--) for (int p = P; p >= 0; p--) {
double c = dp[r][s][p], tot = r * s + s * p + p * r; if (c == 0) continue;
if (tot == 0) { ans[r ? 0 : s ? 1 : 2] += c; continue; }
if (s) dp[r][s - 1][p] += c * r * s / tot; // rock beats scissors
if (p) dp[r][s][p - 1] += c * s * p / tot; // scissors beat paper
if (r) dp[r - 1][s][p] += c * p * r / tot; // paper beats rock
}
return ans;
}Self-loops and cycles
Self-loop: gives . In particular, the expected number of trials until a success of probability is .
General cycles: the system is solved by Gaussian elimination in (modulo a prime, or in doubles with partial pivoting) (Gaussian Elimination). Absorbing Markov chains: with transient block , the expected steps are and the absorption probabilities .
Line or tree structure: express each value as a linear function of its parent’s value, , computed bottom-up, then evaluate top-down. (times a modular inverse per vertex).
Proof: the tree elimination and the hitting time
Random walk on a tree moving to a uniform neighbour, target = the root. For a vertex with degree and children , . Suppose every child satisfies (true for leaves with ). Substituting, , so with , (the denominator is positive since by induction , see below). At the root is known, and values propagate down.
Closed form: the expected time to go from to its parent satisfies (each step into child costs the return time plus starting over), so , . By induction , hence . The expected time from to the root is the sum of along the path.
Decisions with restarts: fixed points
“Play optimally, you may restart at any time; minimise the expected total time” (speedrun problems): the value of the start state appears inside the recurrence through the restart option, where is computed by an ordinary DP that uses as the cost of restarting. is nondecreasing with slope (as long as some run finishes with positive probability without restarting), so binary search on : if , the answer is below .
Proof: binary search on the fixed point
is the optimum of finitely many strategies, each of the form where = probability of restarting under strategy ; a minimum of nondecreasing linear functions is nondecreasing and concave. If some strategy has , then is strictly decreasing for large , and since is concave, it crosses zero exactly once from above: before the true answer and after. The true expected value is that fixed point, because the optimal policy’s value satisfies and any other fixed point would contradict uniqueness. So “is ?” is a monotone predicate in .
A few closed forms worth having memorised rather than re-derived:
| Problem | Answer |
|---|---|
| expected rolls to see all faces (coupon collector) | |
| expected number of comparisons in quicksort | |
| expected length of the longest run of heads in flips | |
| expected number of inversions in a random permutation | |
| expected number of cycles in a random permutation | |
| random walk on , absorbing at both ends, start | reaches w.p. ; expected time |
| birthday paradox | collision after draws |
Hidden Markov models: Viterbi and forward
Most likely hidden state sequence for observations : , with backpointers; . Replacing by log-sum-exp (or by in probability space) gives the forward algorithm, i.e. the total probability of the observations: the same trellis in a different semiring.
vector<int> viterbi(const vector<int>& obs, const vector<double>& logPi, const vector<vector<double>>& logA, const vector<vector<double>>& logB) {
int T = sz(obs), S = sz(logPi);
vector<vector<double>> dp(T, vector<double>(S, -1e18)); vector<vector<int>> par(T, vector<int>(S, -1));
for (int j = 0; j < S; j++) dp[0][j] = logPi[j] + logB[j][obs[0]];
for (int t = 1; t < T; t++) for (int j = 0; j < S; j++) for (int i = 0; i < S; i++) {
double v = dp[t - 1][i] + logA[i][j] + logB[j][obs[t]];
if (v > dp[t][j]) dp[t][j] = v, par[t][j] = i;
}
vector<int> path(T); path[T - 1] = max_element(all(dp[T - 1])) - dp[T - 1].begin();
for (int t = T - 1; t > 0; t--) path[t - 1] = par[t][path[t]];
return path;
}Proof: Viterbi's optimal substructure
By the Markov property, the joint probability of a state sequence and the observations factorises as , so in log space it is a sum of terms each depending on two consecutive states. A best sequence ending in state at time consists of a best sequence ending in some at time plus the last term (otherwise swapping in a better prefix would improve it). This is a longest path in the layered trellis DAG, which the recurrence computes by induction on .
Numerical care
long doublefor products of probabilities; logs when they get tiny.- Avoid iterating a self-referential recurrence to convergence when an algebraic solution exists; when iterating is the only option (value iteration, e.g. rounds for a self-loop structure with a specified target precision), bound the error by the contraction factor.
- Watch for states with total outgoing probability (implicit absorption) and for unreachable states with .
- Modular-answer problems are now the more common convention in contests — check the statement before assuming floating point.
Viterbi’s applications beyond HMM decoding itself: convolutional-code decoding (its original 1967 use, still in modems and satellite links), GPS map-matching (snapping noisy traces to a road network), speech recognition and POS tagging. For huge state spaces, beam search prunes the trellis heuristically instead of keeping every state. Baum–Welch (forward-backward) solves the dual problem of learning the HMM’s parameters rather than decoding with them.