By Soultaker on Tuesday 24 January 2012 01:00 - Comments (13)

Category: Programming Contests, Views: 15.673

Alphabet Soup



1 2 3 4 5 6 7 8 9 10 def solve ( line ): goal = "HACKERCUP" return min ( line . count ( ch ) // goal . count ( ch ) for ch in goal ) # Input/output from sys import stdin T = int ( stdin . readline ()) for t in range ( T ): line = stdin . readline () print ( 'Case # %d : %d ' % ( t + 1 , solve ( line ))) Python:

Billboards



1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def fits ( W , H , L ): 'Returns whether text L fits on a W by H billboard.' pos = 0 for line in range ( H ): pos += W if pos >= len ( L ): return True pos = L . rfind ( ' ' , 0 , pos + 1 ) + 1 return False def solve ( W , H , L ): size = 0 while fits ( W // ( size + 1 ), H // ( size + 1 ), L ): size = size + 1 return size # Input/output from sys import stdin T = int ( stdin . readline ()) for t in range ( T ): W , H , L = stdin . readline () . strip () . split ( None , 2 ) print ( 'Case # %d : %d ' % ( t + 1 , solve ( int ( W ), int ( H ), L ))) Python:

Auction

product 1 2 3 4 5 6 7 8 prices 1 2 1 2 1 2 1 2 weight 3 4 5 3 4 5 3 4

product 1 3 5 7 2 4 7 6 prices 1 1 1 1 2 2 2 2 weight 3 5 4 3 4 3 5 4

Last weekend the qualification round for the 2012 Facebook Hacker Cup was held. Contestants had all weekend to solve at least one from a set of three problems in order to qualify. It turned out two of those were fairly easy, and the last one was pretty hard. I'll address the problems in order of increasing difficulty. If you want to try to solve these problems on your own, stop reading now, because there are spoilers ahead!The last problem in the set was actually the easiest of the three. There are several different ways to approach the problem and with at most a 1000 characters per test case it would be hard to design a solution thatrun in time.For each letter, we can count how often it occurs in the source text and how often it occurs in the target word ("HACKERCUP" for this problem). The quotient of the two is an upper bound on how many times we can reconstruct the target word. After all, to spell "HACKERCUP"times , we needH's,A's,C's (since the letter C occurs twice in "HACKERCUP"), et cetera. Note that we need to round down since we can't use half a letter to spell half a word.A straightforward solution in Python:For this problem we are asked to figure out the largest font size at which a given text still fits on a billboard of fixed dimensions. The tricky part is that we must layout the text ourselves, wrapping lines at word boundaries.The simplest approach is to try increasing font sizes until the text no longer fits. In principle, binary search could be used to optimize this part of the solution, but with the given constraints that's not necessary.That leaves the question of how to determine whether a certain text fits on the billboard. A nice trick is to not scale up the text, but to scale down the billboard and pretend our font always needs exactly 1 unit of length per letter. Then, we simply try to put as many words on each line as possible, truncating lines if necessary to end on a word boundary.There are probably a few different ways to write thefunction that achieve the same result. I chose to use the built-in rfind() function to align the cursor position to the last word boundary. This requires careful indexing to end up at the right location (specifically, we want to place the cursor at the first characterthe last space).The last problem was very hard, even by programming competition standards, due to the large numbers in the test data. Less experienced participants might be tempted to explicitly generate all pairs of prices and weights for all products, and then compare them pairwise, yielding a solution algorithm that runs in quadratic time.Although that approach works for the sample data, the maximum possible number of products in the real test data is extremely large (up to N=10, or a billion times a billion!) and such a solution will not finish in time. In fact, N is so large that even a linear-time (O(N)) algorithm will not run in time.Before we return to efficiency concerns, let's first get a better idea of what good and bad deals look like, by plotting a number of points on 2D graph:As the graph shows, good deals decrease in weight as they increase in price. Similarly, bad deals decrease in weight as they increase in price. This is useful information because it means that if we have a list of products ordered by increasing prices, we only need to go through this list once to determine which are the good deals: exactly those products that have lower weight than the last good deal found. (And similarly for the bad deals, but going through the list in reverse order.)Another useful observation is that for a fixed price point (a horizontal line in the graph) only the product with the lowest weight (i.e. the leftmost point in the graph) can possibly be a "bargain". Similarly, only the product with the highest weight (the rightmost point) can be a "terrible deal". Sometimes these points coincide: look at the yellow dot on the top left.This hints at a possible solution approach. Due to the way prices are generated, there are at most 10different price points, and since we only need to know the minimum and maximum possible weight at that price point, we only need to consider 2×10different price & weight pairs. However, for each price & weight pair, we must also determine how many products exist with those exact properties.To solve this problem efficiently, we need to exploit the fact that the weights and prices of the products are generated by a pair of linear congruential random number generators . As is the case with all pseudo-random number generators, their output is eventually periodic. Since the only state used by a linear-congruential generator is the previously generated value, and these values are in range 1 to M (for prices) or 1 to K (for weights) we can deduce that the generated sequence of prices will become periodic after less than M iterations, and will have a period of at most M (and similarly for weights).Since M (and K) are relatively small we can afford to first peel off the non-periodic part of the generated sequences and then generate an entire period for weights and prices independently. Unfortunately, the periods for weights and prices are likely to be distinct and many not even have a divisor greater than 1 in common, which means that there are potentially near 10distinct price & weight pairs. Although this is much less than the maximum number of products (10) it is still way too many to generate explicitly.To solve this final part of the puzzle, consider that for most prices the sequence of corresponding weights is the same, although we enter the sequence at different positions.This is best illustrated with a short example. Suppose the prices cycle over values [1,2] and the weights over values [3,4,5] and we generate eight products:The price & weight pairs cycle with a period of 6 (since 6 is the least common multiple of 2 and 3) which is why product 1 and 7 have the same properties, as do product 2 and 8. Now let's re-order that table by price:The weights for products with price=1 are 3,5,4,3 etc. and for price=2 they are 4,3,5,4. These sequences are the same, except that the starting weights (3 and 4, respectively) are different!In this example N (the number of products) was greater than the combined period of the generated prices and weights, which means that the minimum and maximum weight are the same for each price. When N is less than that, that is generally not the case. For example, if we generate only 2 products, than the only weight at price=1 is 3, and the only weight at price=2 is 4.We can use these observations to compute each (periodic) sequence of weights only once. Then, for every distinct price point, we only need to find the start and end point in the sequence of weights, figure out the mimimum and maximum weights in that range. The last operation requires some sort of index on the sequence of weights to work efficiently. A tree-based index can be used to solve range queries in logarithmic time, resulting in an O(M log K) algorithm, which is just fast enough for this problem.To avoid making a long explanation even longer, I've glossed over some details; I'm sure readers that have been able to follow the explanation up to this point are able to fill in those details themselves.For those who are curious how I implemented these details, I posted my solution (written in C++) to Pastebin . But beware: it's long and complicated! Source code for other solutions (submitted during the contest) is available from the Hacker Cup scoreboard