December 21, 2022
Útulek Series, 1 | CF1761D Chapter, 1
Codeforces 1761D has been sitting in my head since it was proposed around a month ago.
Consider two -bit binary integers, . Let be the number of carries that occur during the addition of and . For example,
So , and .
For a given , find , the number of ordered pairs such that . For example, for , , as the set of ordered pairs is , , , , , , , , , , , , , , , each of which sees exactly one carry during their addition.
For limits, we have , so anything not is pushing it.
As Wyatt pointed out, the sponsors of the round, the EZEC team at Pinely, also have a very cute mascot in EZEC-chan!
Pinely Rentec > HRT Jane.
It must bring EZEC-chan great disappointment, then, that I wasn’t able to solve 1761D in-contest. Inspired by Wyatt’s deeper (and most of the time, number theoretical) dives into solving problems, such as 1748D, and learning the theory behind them, I attempt the same here—which, more concretely, means exploring everything remotely related and at least somewhat accessible to a (mostly) elementary mathematician. Hence, this “1761D chapter” of blogposts will hopefully cover a lot of ground, from Pell equations and generating functions, to the Taylor series, linear sieve, and Number Theoretic Transform (NTT). Unfortunately, I don’t know much complex analysis yet, so I’ll try to avoid getting in the weeds where possible.
I begin each problem with an attempt to find the correct subproblem structure—that is, a method to compose solutions to smaller subproblems into a solution to a larger subproblem. Trivially, this must exist for any problem solvable with dynamic programming (DP) , but I argue that it exists for any algorithmic problem—the loops themselves establish the sequential subproblems to be solved. For example, in Linear Max Gap, I would retroactively argue that two series of subproblems exist: first, the subproblems of finding the range , and secondly, the subproblems of partitioning and finding the max gap for each of the bucket prefixes .
Playing with small examples, I have discovered, is a decent way to sniff out subproblem structure. We have for the -bit numbers
It makes sense to consider whether the leading bit carries as part of the subproblem, as that obviously affects the number of carries when we consider the next bit. Let denote the number of ordered pairs of bits whose addition shows carries, but with the leading, most significant bit not carried and carried, respectively. We then have , and, considering the -bit numbers, we have
Thus, the subproblems to consider for 1761D , are those of finding for . The structure, then, is that for , either or carries can occur in the -th bit, and the remaining or carries must occur in the less significant bits. Hence,
and it is our mission to find . Intuitively, is linear, as each of should be involved in a constant number of times, based on the bits we select for the -th position. Considering the ordered bit pairs possible for the -th position, we have
We actually need a finer breakdown of into and , but it is not so difficult to use the same casework we have used already to find this:
This I deem a twin two-dimensional recurrence—but I suspect all twin (and likely higher orders) two-dimensional recurrences can be simplified into single two-dimensional recurrences via repeated substitution, as I discovered in-contest. This smells a little like matrices, and we may have to revisit this later on.
for some initial conditions we will determine later. Similarly,
which is frankly quite nice. As , we have then the very simple reduction to
which gives our titular recurrence. We can determine initial conditions by playing around with the small values; this gives us for and for . As is a two-dimensional recurrence, we may visualize it in a Pascal’s triangle-like manner:
with -indexed rows and columns, and at row and column . Indeed, , and the sum of each row is a power of two—as the number of ordered pairs of -bit numbers is .
It’s unfortunately at this point in-contest that I failed to make any additional progress. Naively, computing can be accomplished in via , but this is too slow. Recall that the Fibonacci sequence
which is naively computed in (similarly via memoization), can also be computed in via repeated squaring of its matrix:
or equivalently via repeated squaring of the exponents in Binet’s formula
under assumption of arbitrary precision arithmetic, which is typically an invalid assumption.
I will note, however, that the set of numbers , with , is closed under addition, subtraction, multiplication, and division, and is more generally a field. It must also be that all intermediate operations in computing involve numbers from . I suppose must have a common name, but I am not aware of it. I will further hypothesize that the involved in the operations to compute Binet’s formula likely have simplified numerators and denominators of size based on the following examples:
thus rendering the traditional repeated squaring on Binet’s to actually be with the extra factor coming from dealing with numbers which take bits to represent in memory.
I must also address here that my runtimes are typically stated in reference to as the value of the input, rather than the size (number of bits) of the input, which is typically -size of the value. Some algorithms, such as the vEB tree, are more concisely interpreted with a runtime in terms of the input size, but will primarily not be dealing with those sorts of algorithms.
Anyway, solution to the one-dimensional Fibonacci recurrence is a glimmer of hope that two-dimensional recurrence can be solved in , or, even better, in or . However, I was not able to come up with such a solution in-contest.
Tony and I collaborated on a combinatorial solution after the contest. I will hereby put forth the (perhaps yet immature) hypothesis that the subproblem structure discovered by an attempt is largely determined by the initial examples that one plays with—in the same way that a neural network classifier may be largely determined by the initial seed. Anyway, consider the following initial seed:
This example stinks of the same subproblem structure as in section . I explain below.
When we add a most significant bit to each of the above -bit numbers (hence forming a pair of most significant bits), we see a new carry iff the bit pair is , or . Similarly, if the leading bit were not carried, we would not see a new carry iff the bit pair is , or . This observation, that whether or not the -th bit carries only depends on the carry status of the -th bit, is a case of state space simplification. That is, the carry status of the bits doesn’t influence whether or not the -th bit carries. So, when we move from considering the -th bit to considering the -th bit, we don’t have to store information in our state, but only information.
state information is the best one can ask for—so, if we were to consider the states and subproblems as the sum operations of two -bit numbers for , then we process state information in total, which is within our runtime bounds. This suggests that this subproblem structure is a promising approach. However, attempting a DP solution from this point lands us back with recurrence , which suggests that there are additional simplifications to be made. Indeed, the example is much longer than the examples we used in section , and lends itself to additional insights.
Let’s continue with example . We can represent the carry status of the addition operation of two -bit integers as the following -bit string representation:
At this point, this problem smells like the string runs simplification. I will explain below.
Three of the four choices of bit pairs for the -th bit always continues the carry status of the -th bit, and one choice always flips the carry status. How many ordered pairs generate carry status string like representation ?
The carry status always starts out at , so one flip will need to be made at index . Then, two indices later, another flip, and two indices later, another flip. The final flip occurs at index :
These four flips only have one choice each for the bit pair at their index. Indeed, in , the flips to carry status necessarily have the bit pair at that index, and the flips to carry status necessarily have the bit pair at that index:
So, to create representation there are choices for the four flips, and choices for the four non-flip indices, for a total of ordered pairs of . This is a nice result! We can stop here and, with some work, derive a nice closed form for , but another state space simplification is possible to make that work easier.
With the carry status string representation in , we can derive the number of carries from the string which has bits of information. I’ve come to find that the bits of information in a representation is a poor heuristic for the ability of a representation to be further simplified—in fact, the count of objects in a representation is typically a better heuristic. Still, representation has objects of information. Representation transforms this -bit number into numbers each representing the number of bits before the next flip. This is objects, which is always less than, and better than objects.
Finally, since we only care about the number of carries, we need only store the information about the lengths of the -carry-status runs, which is a total of objects of information! This gives the following string runs representation of example :
where the denotes the run of 1-carry-statuses in indices through , and the denotes the run of 1-carry-statuses in indices through . Indeed, carries as we saw in example .
The string runs representation is generally pretty powerful, and I’ve seen it many times before, most recently in 1767C. There, again, we have binary strings which can be simplified into runs, and furthermore, only the final run matters, lending itself to yet another state space simplification.
Now we’re in the home stretch, which unfortunately is a ton of precise combinatorics. I always find combinatorial solutions difficult to follow unless I work through them myself, and unfortunately I don’t think I’ve done a good enough job here that this isn’t the case.
Taking our state as the runs representation in , we must ask next: how many correspond with each representation? For runs ( in ), we must have flips ( in ), which leads to or possible choices of which generate the flips representation in . The off-by-one difference is due to the position of the final flip—if it is in bit , it frees up an additional bit in the first bits for which we have choices.
As discussed, string run representation contains slightly less information, and thus we expect even more ordered pairs to generate the same representation. Indeed, we no longer know the positions of the -carry-status bits, of which there are . We must somehow first place the runs in with the -carry-status bits, and then scale by either or .
Smell something? Yeah, that’s America stars and bars (twice-fold).
Please stop asking if I receive unemployment benefits.
Supposing we wanted carries, then, it suffices first to consider the number of runs we use:
and for each selection of runs, their positioning with the -carry-status bits:
and how those carry status bits are distributed among the runs:
and finally the or bits which have choices each. Recall that the case only arises if the final run also includes the final, most significant bit, which leaves run positions which generate this case:
Indeed, we have in accordance with ,
We can also re-index to not use the number of runs, but instead the number of non-leading flips:
Admittedly, there is an edge case where as seen in , which must set as . Otherwise, this is a pretty straightforward implementation. The answer may be large, so we are asked to mod by the prime . The factorials up to can be memoized in , and their inverses naively memoized in . Queries are then to compute the -term summation, leading to a solution.
The extra factor feels unneeded—indeed, there is an optimization to memoize the factorial inverses in : by starting with , and multiplying down: Codeforces submission, which is almost 10 times as fast! This also implies that we can find the first multiplicative inverses in , which is indeed the case as explained in this comment by mnaeraxr and in an alternate form in this comment by it4.kp.
The important thing here, of course, isn’t the specifics of the combinatorial argument which landed us at , but rather the form of . Evidently, it must be the same solution which solves recurrence , which, upon retrospection, would have been difficult to derive in-contest.
So, at this point, if the smoke detector isn’t going off, it ought to be replaced. The room is absolutely pungent of something fishy, and frankly, I’m quite the fan of mermaids.
Symbolism.
Here’s why. Recall Pascal’s triangle, the classical two-dimensional recurrence:
with well-known solution . Is it a coincidence, that , like the solution to derived in , both contain choose expressions (and optionally, the sum of many choose expressions)?
Recall that Binet’s formula for the Fibonacci numbers is of the form
so perhaps it is not so surprising to learn that the solution to the tribonacci numbers is of the form
Anyway, I’m going to take a small detour to introduce a problem I read in Mosteller’s Fifty Challenging Problems in Probability:
A drawer contains red socks and black socks. When two socks are drawn at random (without replacement), the probably that both are red is . How many socks can the drawer contain?
I promise this detour does tie back into 1761D. However, that will have to wait until next time.