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

QuestionRecurrence in topological order
longest / shortest path (any weights)dp[v] = max/min(dp[u] + w)
number of pathscnt[v] += cnt[u]
number of shortest pathscarry (dist, count), reset the count on strict improvement
reachability for all pairsreverse order, `reach[u]
lexicographically smallest optimal pathbuild backwards so the choice at each vertex is local
cyclic graph, value collected per vertexcondense 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):

ProblemThe DAG
grid pathscells; edges right and down
LCS pairs; edges advance one or both indices
LISindices; edge when and
knapsack(item, capacity)
interval DPintervals; edges to shorter sub-intervals
bitmask DPsubsets; edges add one element
course scheduling / build systemsprerequisites / 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).

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 .

A few closed forms worth having memorised rather than re-derived:

ProblemAnswer
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 paradoxcollision 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;
}

Numerical care

  • long double for 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.