Codeforces
CF Step
Youtube Linkedin Discord Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Extending Binary Strings with DP

Prerequisite

All the examples below use Distributing DP to extend the strings. If you’ve only solved problems with Receiving DP so far, this style might be difficult to comprehend. You can solve ABC179D: Leaping Tak and ABC335F: Hop Sugoroku in O(n^2) to get accustomed to this style of DP. I also created a video editorial for the latter.

As a refresher, this is Receiving DP

dp[i][j] = something

This is Distributing DP

dp[i + x][j + y] += something + dp[i][j] + otherthing

Application 1 : Unconstrained Binary Strings

Problem Link

Find the number of binary strings of length n.

Define dp[i] to be the number of binary strings of length i. You can extend this string by appending either a 0 or 1. Hence,

// Append a zero.
dp[i + 1] += dp[i]

// Append a one.
dp[i + 1] += dp[i]

You can find the code here

Application 2 : Binary Strings with no Consecutive Zeroes

Problem Link

Call a binary string good if it has no consecutive zeroes. Find the number of good binary strings of length n.

If you define dp[i] to be the number of good binary strings of length i, you won’t be able to extend this string without knowing the character at the last position. This hints us that we also need to track the last character as a parameter in our DP.

Define dp[i][char] to be the number of good binary strings ending at char. If char = 1, we can append either a 0 or 1, but if char is 0, we are forced to append a 1.

// Append a 1
dp[i + 1][1] += dp[i][1]
dp[i + 1][1] += dp[i][0]

// Append a 0
dp[i + 1][0] += dp[i][1]

You can find the code here

Application 3 : Binary Strings with no K-Zero Stream

Problem Link

Call a binary string good if it doesn’t contain k consecutive zeroes as a substring. Find the number of good binary strings of length n.

Just tracking the last character isn’t sufficient anymore. We can only safely extend the string when we know the count of suffix zeroes. Define dp[i][x] to be the number of good strings that has x suffix zeroes. Then, if x == k - 1, we are forced to append a 1, else we can append either a 0 or 1. Notice that if you append a 0, the suffix zero count increses, but if you append a 1, the suffix zero count is reset.

// Append a 1.
dp[i + 1][0] += dp[i][x]

// Append a 0.
if(x + 1 < k) {
    dp[i + 1][x + 1] += dp[i][x]
}

You can find the code here

Application 4 : Hard Version of K-Zero Stream

Problem Link

Call a binary string good if it doesn’t k consecutive zeroes as a substring and it has no substring 000...1...000 such that if there are x zeroes to the left of 1 and y zeroes to the right of 1, then x + y should not be equal to k. Find the number of good binary strings of length n.

Now, tracking the count of suffix zeroes is not sufficient anymore, because you can only extend the string if you also know the count of zeroes to the left of the last occurrence of 1.

Define dp[i][x][y] to be the number of good strings whose suffix has x zeroes, followed by a 1, followed by y zeroes. Then, you can append 1 anytime, but you can append a 0 only if x + y + 1 != k. If you append a 1, the suffix zeroes would be reset, and x would be replaced by y. If you append 0, only y would increase.

// Append a 1.
dp[i + 1][y][0] += dp[i][x][y]

// Append a 0.
if(x + y + 1 < k) {
    dp[i + 1][x][y + 1] += dp[i][x][y]
}

You can find the code here

Application 5 : Dropping Parameters from DP

Problem Link

Remember, earlier we told you that extending a string is not possible without storing additional states such as x and y? Turns out, it is indeed possible, if you do the transitions in bulk.

For the K-Zero Stream (Hard Version) : Define dp[i][x] to the be the number of good binary strings that has a suffix consisting of x zeroes. We are not allowed to track y anymore. So, to ensure that x + y < k, we need to do all transitions at once because we would forget those information if we do it step by step.

If the current string consists of x suffix zeroes, then we can try to extend this string by adding 1 or 10 or 100 or 1000 or 1000... where the new string has a suffix consisting of y zeroes. Then, dp[i][x] would contribute to dp[i + 1 + y][y] for all y such that x + y < k.

// Append 1 followed by y zeroes
for y = 0; x + y < k; y++ {
    dp[i + 1 + y][y] += dp[i]
}

You can find the code here

If you’re looking for some more problems where dropping a parameter seems impossible at first, but is actually possible, try ABC334F: Christmas Present 2. I also created a video editorial for it. Here, you might think that it’s impossible to distribute gifts if you don’t keep a parameter to track how many presents are remaining when you reach the i-th child, but you’ll love the cleverness of the final DP definition.

Application 6 : CF1920E : Counting Binary Strings

Easy Version : Problem Link Original Version : Problem Link

If you’ve really understood the tutorial so far, you should close this page and solve this problem in O(n^3) time and O(n^2) space by yourself. You can come back to it for the final optimized solution.

Suppose we have a string containing g good substrings. How does g vary when we append an element?
Define dp[i][x][y] to be the number of strings that has i good substrings and has a suffix that consists of x zeroes, followed by a single one, followed by y zeroes. Extend these strings using the techniques discussed in this blog. Then, drop the y parameter.

Suppose we have a string containing g good substrings. How does g vary when we append an element?

If you append 1, then the uncounted good substrings are those that end at this 1. If there are y suffix zeroes, then a total of y + 1 new good substrings would appear.

If you append 0, then the uncounted good substrings is equal to 1 + the number of consecutive zeroes to the left of the last occurrence of 1.

Define dp[i][x][y] to be the number of strings that has i good substrings and has a suffix that consists of x zeroes, followed by a single one, followed by y zeroes.

Hence, we need to track 2 parameters to quickly compute the answer. Define dp[i][x][y] to be the number of strings that has i good substrings and has a suffix that consists of x zeroes, followed by a single one, followed by y zeroes.

int nxt = i + 1 + y
// Append a 1
dp[nxt][y][0] += dp[i][x][y]

// Append a 0
if(x + y + 1 < k) {
    int nxt = i + x + 1
    dp[nxt][x][y + 1] += dp[i][x][y]
}

You can find the code here

Now, how do we drop the y parameter from DP? Define dp[i][x] as the number of strings that contain i good substrings and has exactly x suffix zeroes. We can try to extend this string by adding 10000... where the new suffix zeroes would have length y. Of course, x + y < k to ensure that we don’t create a good substring of length greater than k.

Now, if you add add 1 followed by y zeroes, what good strings can be formed? Of course, this new 1 has to be present in all those substrings. Then, it can contain any element from the left half (or skip it), hence x + 1 choices, and it can independently select any element from the right half (or skip it), hence y + 1 choices. Also, to ensure that no good substring of length > k is formed, we should keep x + y < k.

Then, the total number of good substrings that would appear after doing these transitions in bulk is (x + 1)*(y + 1).

// Append 10000... (y zeroes at the end)
for(int y = 0; x + y < k; y++) {
    int nxt = i + (x + 1)*(y + 1)
    if(nxt <= n) {
        dp[nxt][y] += dp[i][x]
    }
}

You can find the code here

Finally, how do we improve time complexity to better than O(n^3)? Nottice that i + (x + 1)*(y + 1) would quickly exceed n as x grows.

For x = 1, we would add contribution to n elements, for x = 1, we would add contribution to n/2 elements, for x = 2, we would add contribution to n/3 elements, and so on…

Hence, the sum is bounded by Harmonic Series sum O(N Log(n)) if you exit from the loop everytime you exceed n.

// Append 10000... (y zeroes at the end)
for(int y = 0; x + y < k; y++) {
    int nxt = i + (x + 1)*(y + 1)
    if(nxt <= n) {
        dp[nxt][y] += dp[i][x]
    } else {
        break
    }
}

You can find the code here

Answer to Bonus Problems

How to drop parameter for binary strings no consecutive zeroes?

If dp[i] represents the number of binary strings with no consecutive zeroes, then you can extend this string by appending 1 or 10.

// Append a 1
dp[i + 1] += dp[i]

// Append a 10
dp[i + 2] += dp[i]

How are strings with no consecutive zeroes and Fibonacci number related?

Note that a string of length i can be created by appending a 1 to dp[i - 1], or appending a 10 to dp[i - 2]. Hence, it’s equal to the Fibonacci number.

How to drop parameter for no k zero stream?

Extend the string of length i by appending 1 followed by x zeroes.

for x = 0; x < k; x++ {
    dp[i + 1 + x] += dp[i]
}

How to find strings with atleast 3 Consecutive zeroes?

Solve for the complement. Find strings with no 3-consecutive zero stream, then subtract it from 2^n.

Source

Credits

Thanks to aryanc403 for showing me the trick of dropping parameters from DP and doing transitions in bulk.