SRM 728, D1, 250-Pointer (Halving)

James S. Plank

Tue Nov 12 17:30:55 EST 2019

In case Topcoder's servers are down

Here is a summary of the problem:

Examples

The driver in main.cpp has the examples compiled in, and if you enter "-" as a command line argument, you can read the values of the stick lengths on standard input.

Testing yourself

Like the Cryptography Problem, I have a shell script tests.sh, that you can use to test your program. When you run tests.sh, your answer should be identical to answers.txt. Mine runs in about 2.5 seconds.

Observation #1

Let's suppose we have one of these stick lengths. Call it s. How many different lengths can it create? I'm going to propose that the answer is under 100. Think about it -- suppose that s is roughly 230. Then, when you halve it, you're going to get one or two numbers that are around 229. Suppose you now have two numbers from halving. When you halve them, you are only going to get two more distinct numbers. There won't be more than a few distinct numbers for each round of halving. For example:

17 -> 8   -> 4   -> 2   -> 1   -> 1
   -> 9   -> 5   -> 3   -> 2
You can get more than two per round, but it won't be by much. To verify this, I wrote a quick and dirty program that calculated how many lengths were generated by each s from 1 to 100,000. The maximum value was 33. That's a proof by cosmo, but I'll take it.

Strategy

Given this observation, your strategy should be straightforward: Let's go over example zero to illustrate. Sticks 11 and 4 may be broken as follows, which will generate the two maps canbreak and numbreaks:
   Stick 11          Stick 4        canbreak       numbreaks
--------------    --------------    ---------      ---------
Length  Breaks    Length  Breaks    Key   Val      Key   Val 
  11       0         4       0       11     1       11     0
   6       1         2       1        6     1        6     1
   5       1         1       2        5     1        5     1
   3       2                          4     1        4     0
   2       2                          3     1        3     2
   1       3                          2     2        2     3
                                      1     2        1     5
As you can see, the only stick lengths where canbreak is equal to the number of sticks are 2 and 1. Of these, the length with the minimum value of numbreaks is 2, whose val is 3, so the answer is 3.


A recursive procedure to call on each stick length

I wrote a recursive procedure with the following prototype:

void all_breaks(int length, int breaks, map <int, int> &breakmap);

For each stick length len in the input array, I called all_breaks(len, 0, m), where m was an empty map.

What all_breaks() does is check to see if the length is in the map. If it is, and if its val is less than or equal to breaks, then all_breaks() returns. That's its base case.

If it doesn't return, then it either adds length to the map with a val of breaks, or it updates length's entry in the map to equal breaks. Then, if length is greater than one, it calls itself recursively on the two values that you get when you break length. (You'll note that if length is even, it gets two of the same lengths, but you don't really need to test for this -- if you make the same recursive call twice, it will return instantly on the second one, because the first one has put the length into the map.).

When all_breaks(len, 0, m) returns, the map m will contain all of the information that you need to update canbreak and numbreaks.

If the recursion is confusing to you -- here's what happens when you call all_breaks(11, 0, {}):

all_breaks(11, 0, {}) puts [11,0] into the map and then calls all_breaks recursively on 5 and 6.
  all_breaks(5, 1, {[11,0]}) puts [5,1] into the map and then calls all_breaks recursively on 2 and 3.
    all_breaks(2, 2, {[5,1],[11,0]}) puts [2,2] into the map and then calls all_breaks recursively on 1 and 1.
      all_breaks(1, 3, {[2,2],[5,1],[11,0]}) puts [1,3] into the map and returns.
      all_breaks(1, 3, {[1,3],[2,2],[5,1],[11,0]}) returns because 1 is in the map.
    all_breaks(2, 2, ..) returns
    all_breaks(3, 2, {[1,3],[2,2],[5,1],[11,0]}) puts [3,2] into the map and then calls all_breaks recursively on 2 and 1.
      all_breaks(2, 3, {[1,3],[2,2],[3,2],[5,1],[11,0]}) returns, because 2 is in the map with a smaller value.
      all_breaks(1, 3, {[1,3],[2,2],[3,2],[5,1],[11,0]}) returns, because 1 is in the map with the same value.
    all_breaks(3, 2, ..) returns
  all_breaks(5, 1, ..) returns
  all_breaks(6, 1, {[1,3],[2,2],[3,2],[5,1],[11,0]}) puts [6,1] into the map and then calls all_breaks recursively on 3 and 3.
    all_breaks(3, 2, {[1,3],[2,2],[3,2],[5,1],[6,1],[11,0]}) returns, because 3 is in the map with the same value.
    all_breaks(3, 2, {[1,3],[2,2],[3,2],[5,1],[6,1],[11,0]}) returns for ibid.
  all_breaks(6, 1, ..) returns
all_breaks(11, 0, ..) returns

The final map is {[1,3],[2,2],[3,2],[5,1],[6,1],[11,0]}.  You'll note how it matchs the "stick 11" table above.

Putting the pieces together

I would start by writing all_breaks() and testing it by printing it out -- you can see the resulting maps for 11 and 4 above. For 1000000000, the map is in the file big.txt. You'll note that there are 50 elements in the map.

After you are sure you have written all_breaks() correctly, go ahead and finish the program.


Running time

Calculating this one's running time is a pain to do in detail. However, with the maximum stick length capped at 10, this means that the maximum size of breakmap will be roughly 40. That allows us to saly that all_breaks() will run in O(1) time. If the number of sticks is n, the size of canbreak and minbreaks will be capped at 40n, which is O(n).

This means that the running time is n times the sum of:

So this is O(n log n).