Advent of Code 2023 - Day 12Hot Springs

R | Problem statement | Source code | Tags: Dynamic programming

Because we are making a series of decisions (do I fill the ? with # or .?) and each one determines what decisions we can make next (in order to satisfy the arrangement given), this strongly suggests dynamic programming.

We iterate through each spring in the condition records and build groups one by one. The state consists of the current group's index jj (1jm1\le j\le m, mm is the number of groups) and size ss (0smax(arrangements)0\le s\le \max(\text{arrangements})). s=0s=0 indicates an "idle" state, i.e., not actively adding springs to the current group. We count how many ways there are to reach each state (see 2021 day 6).

Initially we start at state j=1,s=0j=1, s=0 (want to construct the first group, but no springs added yet). Because in R indices start at 1, all indices into dp are shifted by 1, so 1, 1 corresponds to j=1,s=0j=1, s=0.

dp <- matrix(0, nrow = m + 1, ncol = max_seg_len + 1)
dp[1, 1] <- 1
R

Given a current state, we can do one of the following transitions:

for (ch in cur_springs) {
next_dp <- matrix(0, nrow = m + 1, ncol = max_seg_len + 1)

for (j in 1:(m + 1)) {
for (s in 0:max_seg_len) {
ways <- dp[j, s + 1]
if (ways == 0) {
# Unreachable state
next
}
if (ch == "." || ch == "?") {
if (j <= m && s == expected_counts[j]) {
next_dp[j + 1, 0 + 1] <- next_dp[j + 1, 0 + 1] + ways
} else if (s == 0) {
next_dp[j, 0 + 1] <- next_dp[j, 0 + 1] + ways
}
}
if (ch == "#" || ch == "?") {
if (j <= m && s < expected_counts[j]) {
next_dp[j, s + 1 + 1] <- next_dp[j, s + 1 + 1] + ways
}
}
}
}
dp <- next_dp
}
R

The valid ending states are those where we have built all mm groups and the m+1m+1-th group is idle (j=m+1,s=0j=m+1, s=0), or we have built m1m-1 groups and the mm-th group is active with the expected size (j=m,s=expected_counts[m]j=m, s=\text{expected\_counts}[m]). We sum these two states to get the final answer.

dp[m + 1, 0 + 1] + dp[m, expected_counts[m] + 1]
R

Parts 1 and 2 are exactly the same, except that in part 2 there's some preprocessing with the input, and the count is so big that we need gmp again for the total. (But each count_dp still fits in a 32-bit integer.)