State = a contiguous range . Transitions either split at some (dp[l][k] and dp[k][r]), or peel an end (dp[l+1][r], dp[l][r-1]). states × splits = , fine for –. Iterate by increasing length (or decreasing, increasing) so both halves are ready.

for (int len = 2; len <= n; len++)
    for (int l = 0, r = len; r <= n; l++, r++) {
        dp[l][r] = INF;
        for (int k = l + 1; k < r; k++) dp[l][r] = min(dp[l][r], dp[l][k] + dp[k][r] + cost(l, k, r));
    }

Catalogue

ProblemRecurrence
matrix chain ()
merging adjacent piles (cost = merged size)split, cost ; Knuth makes it ; Garsia–Wachs
optimal BST with access frequenciessplit at the root, cost ; Knuth (Knuth)
convex polygon triangulationsplit at the apex of the triangle on edge ; cost is the triangle’s weight (area, perimeter, whatever the problem charges)
burst balloonschoose the balloon burst last: (exclusive bounds)
longest palindromic subsequencepeel: ? :
minimum insertions to make a palindrome
take from either end, two players, maximise difference
strange printer (min strokes to paint a string)merge with an equal character, below
removing boxes ( per block)add a dimension: extra equal boxes attached on the right
longest regular bracket subsequence / colouring bracketssplit at the partner of
context-free grammar membership (CYK)split, over nonterminals
circular arraysdouble the array, answer (cost becomes from — check the limits before committing)
zero-out a range with “decrement a subarray” moves, no DP needed

Choosing the last operation

When the first decision makes the two sides interact (bursting a balloon makes its neighbours adjacent), decide the last event instead: everything else in the range has already happened, and the boundaries are still present, so the two sides are independent.

Games on an array

= (score of the player to move) − (score of the other) on the remaining range. Taking leaves the opponent to move on with advantage from their point of view:

With an even number of elements, the first player can always guarantee at least half (take all odd or all even positions), but the DP gives the exact value. See Game DP for the general theory this is a special case of.

Strange printer

Minimum strokes to print , where a stroke paints a contiguous segment with one colour over whatever is there:

int strangePrinter(const string& s) {
    int n = sz(s); vector<vector<int>> dp(n + 1, vector<int>(n + 1, 0));   // dp[i][j+1] for s[i..j]
    for (int i = n - 1; i >= 0; i--) for (int j = i; j < n; j++) {
        int& d = dp[i][j + 1]; d = dp[i][j] + 1;
        for (int k = i; k < j; k++) if (s[k] == s[j]) d = min(d, dp[i][k + 1] + dp[k + 1][j]);
    }
    return n ? dp[0][n] : 0;
}

Removing boxes

dp[l][r][k] = best score for boxes when boxes equal to box are glued after :

, fast in practice. The “glued extra items” dimension is the general fix when merging blocks of equal elements is beneficial (Zuma-like problems).

Brackets and grammars

  • Longest regular bracket subsequence: .
  • Counting colourings of a given regular sequence (each pair: one bracket coloured, adjacent coloured brackets differ): DP over the matching tree, .
  • CYK (grammar in Chomsky normal form): can[l][r][A] iff ; combine can[l][m][B] and can[m][r][C] for each rule . ; with bitsets over nonterminals, faster. Counting parse trees is the same loops with sums and products. A general grammar must be converted to CNF first (eliminate and unit rules, binarise any right-hand side longer than 2) before CYK applies.
// rules: term[A] = set of chars, bin = {A, B, C} for A -> B C
bool cyk(const string& s, int K, int start, const vector<vector<char>>& term, const vector<array<int, 3>>& bin) {
    int n = sz(s); if (!n) return false;
    vector<vector<vector<char>>> can(n, vector<vector<char>>(n + 1, vector<char>(K)));
    for (int i = 0; i < n; i++) for (int A = 0; A < K; A++) can[i][i + 1][A] = term[A][(unsigned char)s[i]];
    for (int len = 2; len <= n; len++) for (int l = 0; l + len <= n; l++) for (int m = l + 1; m < l + len; m++)
        for (auto [A, B, C] : bin) if (can[l][m][B] && can[m][l + len][C]) can[l][l + len][A] = 1;
    return can[0][n][start];
}

Speedups

ConditionTechniqueResult
cost satisfies the quadrangle inequality and is monotone on inclusionKnuth:
merging piles in fixed order (alphabetic trees)Garsia–Wachs / Hu–Tucker
only “interesting” intervals (e.g. split by the maximum)DP on the Cartesian tree–
matrix chainHu–Shing (convex polygon partitioning), rarely needed

Matrix chain does not satisfy the quadrangle inequality in general, so Knuth’s optimisation is invalid there; it is valid for optimal BST and pile merging. This is a common mix-up — Knuth’s own result was specifically for optimal BSTs, whose cost (sum of frequencies) is Monge, not for matrix chain’s , which isn’t.

When merge order can be chosen freely (the leaves may be permuted, e.g. picking which items to merge first with no fixed left-to-right sequence), plain greedy Huffman coding solves the version directly; when the order is fixed (as in matrix chain, optimal BST, polygon triangulation), you need Hu–Tucker/Garsia–Wachs or a Knuth-optimised DP instead — matrix chain is the hard case precisely because its order is fixed and its cost isn’t Monge.

Polygon triangulation and matrix chain are the same recurrence, and the correspondence is a genuine bijection: each parenthesisation of the matrix product corresponds to a triangulation of a convex -gon, which is why the number of parenthesisations (equivalently, triangulations) is the Catalan number (Catalan Numbers).

Interval DP in the wild: optimising a chain of matrix multiplications, database join-order optimisation (same shape as matrix chain), tensor-contraction ordering in ML frameworks.