13.15 Counting Dynamic Programming
Many dynamic programming problems ask for the best solution: ```text minimum cost maximum value
13.15 Counting Dynamic Programming
Many dynamic programming problems ask for the best solution:
minimum cost
maximum value
shortest path
Counting dynamic programming asks a different question:
How many valid solutions exist?
Instead of searching for an optimum, we count every valid configuration while avoiding duplicate work. The resulting techniques appear throughout combinatorics, graph theory, string algorithms, probability, scheduling, cryptography, and competitive programming.
Counting problems are often deceptively difficult. Enumerating all solutions may require exponential time, but dynamic programming can frequently count them in polynomial time by recognizing overlapping subproblems.
The key insight is that we usually do not need to generate every solution. We only need to know how many there are.
Problem
How do we count large numbers of valid solutions without explicitly generating them?
Understanding Counting States
The most common state meaning is:
$$ dp[state] $$
Meaning:
The number of valid ways to reach or construct this state.
Examples:
number of paths
number of partitions
number of subsequences
number of schedules
number of valid configurations
Unlike optimization DP:
take minimum
take maximum
counting DP usually combines transitions using addition.
This simple observation explains many counting recurrences.
Example: Climbing Stairs
Suppose:
You may climb
1 or 2 steps.
Question:
How many ways
can you reach step n?
State:
$$ dp[i] $$
Meaning:
number of ways
to reach step i
To arrive at:
$$ i $$
the previous step must be:
$$ i-1 $$
or:
$$ i-2 $$
Therefore:
$$ dp[i] = dp[i-1] + dp[i-2] $$
Base cases:
$$ dp[0]=1 $$
$$ dp[1]=1 $$
This is the classic Fibonacci recurrence.
The key observation:
Counts from independent transitions are added together.
Why Addition Appears
Suppose:
100 ways
reach state A
50 ways
reach state B
If every solution reaching A can continue to the target, and every solution reaching B can continue independently, then:
total ways
=
100 + 50
Counting DP almost always follows this principle.
Independent sources of solutions contribute additively.
Example: Grid Paths
Consider an (m \times n) grid.
Allowed moves:
right
down
State:
$$ dp[i][j] $$
Meaning:
number of paths
to cell (i,j)
Every path arrives from:
- above
- left
Recurrence:
$$ dp[i][j] = dp[i-1][j] + dp[i][j-1] $$
Base conditions:
$$ dp[0][j]=1 $$
$$ dp[i][0]=1 $$
The recurrence mirrors the physical structure of the problem.
Example: Coin Change Counting
Given coins:
1
2
5
Question:
How many ways
can amount n
be formed?
State:
$$ dp[x] $$
Meaning:
number of ways
to create amount x
Transition:
$$ dp[x] += dp[x-coin] $$
Implementation:
dp[0] = 1
for _, coin := range coins {
for amount := coin;
amount <= target;
amount++ {
dp[amount] +=
dp[amount-coin]
}
}
Notice:
+=
rather than:
min
max
The goal is accumulation, not optimization.
Order Matters Versus Order Does Not Matter
This distinction is one of the most important concepts in counting DP.
Consider:
coins = {1,2}
target = 3
Order Matters
Valid solutions:
1+1+1
1+2
2+1
Count:
3
Order Does Not Matter
Valid solutions:
1+1+1
1+2
Count:
2
The recurrence may appear similar, but the iteration order changes the interpretation dramatically.
Many counting bugs originate from confusing these two situations.
Example: Counting Subsequences
Given:
ABCABC
Question:
How many subsequences
equal ABC?
State:
$$ dp[i][j] $$
Meaning:
number of ways
first j characters
of target
appear within
first i characters
of source
Suppose:
source[i-1]
=
target[j-1]
Then:
use character
or
skip character
Recurrence:
$$ dp[i][j] = dp[i-1][j] + dp[i-1][j-1] $$
Otherwise:
$$ dp[i][j] = dp[i-1][j] $$
This pattern appears frequently in string counting problems.
Example: Partition Counting
Question:
How many ways
can integer n
be written as a sum?
Example:
4
4
3+1
2+2
2+1+1
1+1+1+1
Answer:
5
A suitable state may track:
remaining value
largest allowable part
This leads naturally to a two-dimensional counting DP.
Many combinatorial counting problems share this structure.
Counting Paths in Directed Acyclic Graphs
Given a DAG:
count paths
from source
to destination
State:
$$ dp[u] $$
Meaning:
number of paths
from node u
to destination
Recurrence:
$$ dp[u] = \sum dp[v] $$
for every outgoing edge:
$$ u \rightarrow v $$
This resembles subtree counting and path aggregation in trees.
The absence of cycles makes dynamic programming possible.
Counting Versus Probability
Probability DP and counting DP often look similar.
Counting:
$$ dp[state] = \sum dp[next] $$
Probability:
$$ dp[state] = \sum p_i \times dp[next_i] $$
The difference is the weighting factor.
Probability distributes fractional mass.
Counting accumulates integer counts.
Understanding this distinction prevents many modeling errors.
Large Numbers
Counting problems frequently produce enormous answers.
Consider:
number of subsets
of 100 elements
Result:
$$ 2^{100} $$
which greatly exceeds standard integer limits.
Common approaches include:
Arbitrary Precision Arithmetic
Use big integers.
Modular Arithmetic
Compute:
$$ answer \bmod M $$
where:
$$ M = 10^9+7 $$
or another prime modulus.
Many contest problems require modular counting.
Modular Counting DP
Recurrence:
$$ dp[i] = (dp[i-1] + dp[i-2]) \bmod M $$
Implementation:
dp[i] =
(
dp[i-1]
+
dp[i-2]
) % MOD
Apply the modulus at every update.
Waiting until the end often causes overflow.
Inclusion-Exclusion and Counting DP
Some counting problems involve overlapping solution sets.
Example:
count valid schedules
where multiple constraints interact.
Dynamic programming may count configurations while inclusion-exclusion removes duplicates.
The two techniques often appear together in advanced combinatorial problems.
Common Counting Patterns
Several state designs appear repeatedly.
Path Counting
$$ dp[node] $$
Examples:
- DAG paths
- grid paths
- maze traversal
Sequence Counting
$$ dp[i] $$
Examples:
- staircase problems
- compositions
- partitions
Subsequence Counting
$$ dp[i][j] $$
Examples:
- pattern matching
- string analysis
Subset Counting
$$ dp[mask] $$
Examples:
- set partitions
- assignment counting
- combinatorial enumeration
Avoiding Double Counting
The most common counting error is counting the same solution multiple times.
Example:
1+2
2+1
Should these be considered different?
The answer depends on the problem statement.
Always determine:
Does order matter?
before designing the recurrence.
A correct recurrence with the wrong interpretation can produce completely incorrect counts.
Common Mistakes
Using Maximum Instead of Addition
Optimization recurrences frequently use:
max
Counting recurrences almost always use:
+
Switching between the two accidentally is common.
Incorrect Base Cases
For counting:
$$ dp[0]=1 $$
often means:
one way
to do nothing
This interpretation is subtle but essential.
Overflow
Counts grow rapidly.
Use appropriate numeric types or modular arithmetic.
Double Counting
Different transitions may generate identical solutions.
Verify that every valid solution is counted exactly once.
Missing States
Counting DP must account for every valid construction path.
Incomplete state definitions silently lose solutions.
Recognizing Counting DP
Strong indicators include:
how many ways
count configurations
number of paths
number of sequences
number of arrangements
count valid solutions
Whenever the output is a count rather than an optimum, counting DP should be considered.
Complexity Analysis
Let:
$$ S $$
be the number of states.
Let:
$$ K $$
be the number of transitions per state.
Time complexity:
$$ O(SK) $$
Memory:
$$ O(S) $$
The asymptotic complexity often matches optimization DP.
Only the aggregation operation changes.
Takeaway
Counting dynamic programming replaces optimization with enumeration. Instead of finding the best solution, each state records the number of valid ways to construct that state. Independent sources of solutions contribute additively, leading to recurrences built around summation rather than minimization or maximization. From path counting and subsequence counting to partitions and combinatorial enumeration, counting DP provides a systematic framework for solving problems whose brute-force solution space may be exponentially large while still producing answers in polynomial time.