Lamps
Abridged problem statment
- You have two binary strings: and .
- You can perform the following three operations:
- Set a range , , with to .
- Set a range , , with to .
- Toggle a range , , with : that is, perform addition modulo .
- Find the minimum number of operations to convert to .
How do we begin? Lucky for us, the problem gives us a good place to start.
Subtask 1:
should instantly remind you of one thing: bitmasks. And, sure enough, you can represent each state of string as a bitmask. We could try dynamic programming, but we’d quickly notice that there would be circular dependencies. But that’s not an issue with something like a breadth-first-search. And actually, that’s the solution.
However, keep in mind that we can’t implement our transitions in (naively). We need to be smart and use bitwise operations (which make our transitions ).
Let us iterate over each pair such that . Let our starting state be .
- Toggling seems to be the easiest, since it is equivalent to addition mod , which is in turn equivalent to the exclusive or (XOR) bitwise operation. We can toggle the state of all bits from to by applying the XOR operation to with (call the latter
mask). - For range setting to , we extract the bits from to by doing
s & mask. Then XORing this with again sets all of the bits in that region to . - The case for is similar, except we need to flip the bits we got with
s & mask. We do this using the~operator, but then we need to re-AND it withmaskto not flip the bits beyond our range. Then we simply XOR this with .
In conclusion, the transitions are:
Subtask 3: Each character in is
Imagine going from something like to . There is one clear solution: simply flip all segments where there are s in in , and you’re done. So that would be . Is this optimal? Turns out, yes.
For the purposes of this short proof, I’ll define ‘fixing’ a segment of s as applying an operation on the corresponding s in to convert them to a segment of s. Also, let be the number of segments of s in .
We already know that our answer is at most . But now imagine we tried to do this in less than operations: we’d have to fix two or more segments with a single operation. If you try to do this, you’d have to flip everything in between, including s. But this introduces yet another segment that you’d have to fix, so even with just two segments, you’d be better off fixing them individually than trying to handle both in one operation.
Full solution
Experimenting slightly with the operations might eventually lead you to noticing that two toggle operations that overlap can be replaced with two toggle operations that don’t overlap.
Of course, similar logic can be used to reach the same conclusion for consecutive overlapping set operations. So if we have an optimal solution with consecutive overlapping and set operations, we can obtain one without. Next, notice that any set operations that come after toggles can be moved before them. Red lines depict set to operations, while blue lines are set to operations.
Case #1: Intervals perfectly overlap
Case #2: Intervals don’t perfectly overlap
With this, we’ve proven that there exists an optimal solution where all set operations occur before all toggle operations. How do we proceed?
Well, it’s natural to divide the process into two steps:
- Go from to some ternary string consisting of (?, , and ) using only set operations, where ? means no set operation has been applied to that character, means that the character has been forced to be a , and means that it has been forced to be a . This ternary string is just our way of representing what happens to the string after applying our set operations. Call this step .
- Go from that same ternary string to using only toggle operations. Call this step .
Step
Like before, we group the s together. It is quite obvious that we can’t perform operations that cross any ? characters, so we can deal with each part individually.
For one part, the optimal strategy is to either first zero out the entire range, then activate s sequentially, or set the entire range to and then deactive ranges to s sequentially.
The reason is: if we didn’t do this, we’d need to deal with each and separately, spending one operation on each one of them. It is better to spend one operation in total for all s or all s at the beginning, and then spend the rest of your operations on the remaining ranges.
The answer for:
- 0 is 1
- 01 is 2
- 010 is 2
- 0101 is 3
- 01010 is 3
- 010101 is 4
- 0101010 is 4
Do you notice a pattern? Yes, in general, the answer is , where is the number of segments!
Step
Form a new string, call it , from the ternary string, replacing each ? at location with . We need to go to using only toggles. Notice how this problem (as stated many times now!) is equivalent to converting to by performing range increment operations, modulo .
So in other words, we want in the fewest amount of increment operations. We can add to each side of this congruency without changing anything: we want . The trick is: we’ll perform this change for all indices where is , thus making .
This converts to a string, and the answer here (as we already determined in subtask ) is the number of segments in the modified (which is just , by the way).
Putting it all together
So now we know how to find the answer for both steps and . How do we use this to solve the problem?
Do we iterate over all possible ternary strings.
No, we can’t actually do that, but what we can do is use dynamic programming. We’ll need to store the index we’re at (), the current character of the ternary string (), and the number of segments (for step ), modulo (). I’m sure you can figure the rest out yourself. If you can’t, read ahead.
Code
Expand
#include <bits/stdc++.h>
int main() {
std::ios_base::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::string a, b;
std::cin >> a >> b;
auto f = [&](int i, int j) {
return i == n ? 0 : (j == 2 ? a[i] - '0' : j) ^ (b[i] - '0');
};
std::vector dp(n + 1, std::vector(3, std::vector<int>(2)));
dp[n][0][0] = dp[n][1][1] = dp[n][0][1] = dp[n][1][0] = 1e8;
for (int i = n - 1; i >= 0; --i) {
for (int k = 0; k < 2; ++k) {
dp[i][0][k] = std::min(
{dp[i + 1][0][k] + (f(i, 0) and !f(i + 1, 0)),
dp[i + 1][1][!k] + !k + (f(i, 0) and !f(i + 1, 1)),
dp[i + 1][2][0] + !k + (f(i, 0) and !f(i + 1, 2))});
dp[i][1][k] = std::min(
{dp[i + 1][0][!k] + !k + (f(i, 1) and !f(i + 1, 0)),
dp[i + 1][1][k] + (f(i, 1) and !f(i + 1, 1)),
dp[i + 1][2][0] + !k + (f(i, 1) and !f(i + 1, 2))});
dp[i][2][k] = std::min(
{dp[i + 1][0][1] + (f(i, 2) and !f(i + 1, 0)) + 1,
dp[i + 1][1][1] + (f(i, 2) and !f(i + 1, 1)) + 1,
dp[i + 1][2][0] + (f(i, 2) and !f(i + 1, 2))});
}
}
std::cout << std::min({dp[0][0][1] + 1, dp[0][1][1] + 1, dp[0][2][0]}) << '\n';
}This solution has a time complexity of , and a space complexity of .