Dynamic programming where the state is a position (or a pair of positions) in one or more strings, or a state in an automaton built from a pattern.

The three shapes

1. Two-sequence DP — dp[i][j]

Position in , position in .

ProblemRecurrence
LCSmatch dp[i-1][j-1]+1, else max(dp[i-1][j], dp[i][j-1])
Edit distance1 + min over insert / delete / replace
Longest common substringmatch dp[i-1][j-1]+1, else 0
Regex / wildcard matching* branches into “consume” and “skip”
Interleaving checkdp[i][j] = can and interleave to form the prefix of
Distinct subsequences countdp[i][j] += dp[i-1][j-1] on a match
Shortest common supersequence, then reconstruct

, and space with a rolling array — or Hirschberg if you also need the alignment.

2. Interval DP — dp[l][r]

ProblemRecurrence
Longest palindromic subsequenceends match dp[l+1][r-1]+2, else max of peeling one end
Minimum insertions for a palindrome, or a direct interval DP
Remove boxes / Zumaneeds a third dimension for “attached” elements
Count palindromic subsequencesinclusion-exclusion on the ends
Optimal string parenthesisationsplit at

See Interval DP.

3. Automaton DP — dp[i][state]

Build an automaton from the pattern(s), then do a DP over (length, automaton state). This is the shape for counting strings with constraints.

AutomatonCounts
KMP automatonstrings avoiding one pattern
Aho-Corasickstrings avoiding any of several patterns
Suffix automatondistinct substrings, -th substring
Triestrings from a dictionary
Custom DFAstrings satisfying a regular property
// count strings of length L over an alphabet of size A avoiding the pattern
// aut[state][c] from the KMP automaton; state == m means "matched", forbidden
dp[0][0] = 1;
for (int i = 0; i < L; i++)
    for (int st = 0; st < m; st++)
        for (int c = 0; c < A; c++) {
            int ns = aut[st][c];
            if (ns == m) continue;                     // would complete the pattern
            dp[i+1][ns] = (dp[i+1][ns] + dp[i][st]) % MOD;
        }

When is huge (), the transition is a fixed matrix — use matrix exponentiation over the automaton, giving .

That combination — Aho-Corasick automaton + matrix power — is the standard solution to “count length- strings containing none of these patterns”, and it is worth recognising on sight.

Common building blocks

NeedPrecompute
”Is a palindrome?” in an boolean table, or hashes
”Where does the next occurrence of start?”nxt[i][c] — the next position with character
“Is ?“hashing, or a suffix array + LCP
Longest common extensionLCP via sparse table over the LCP array

The nxt[i][c] table is underused: it turns “find the next matching character” from a scan into an lookup, and it is exactly what makes subsequence-matching DPs and “-th distinct subsequence” problems efficient.

vector<array<int,26>> nxt(n + 1);
nxt[n].fill(n);
for (int i = n - 1; i >= 0; i--) { nxt[i] = nxt[i+1]; nxt[i][s[i]-'a'] = i; }

Counting distinct subsequences

// number of distinct subsequences of s
vector<long long> dp(n + 1, 0);
dp[0] = 1;
vector<int> last(26, -1);
for (int i = 0; i < n; i++) {
    dp[i+1] = 2 * dp[i] % MOD;
    if (last[s[i]-'a'] != -1) dp[i+1] = (dp[i+1] - dp[last[s[i]-'a']] + MOD) % MOD;
    last[s[i]-'a'] = i;
}

The subtraction removes the subsequences double-counted because of a repeated character — a small inclusion-exclusion that is easy to get wrong and worth memorising.

See also: Dynamic Programming · Aho-Corasick · Strings