|
Match Editorial |
SRM 94June 3, 2002
The Problems
DIV-I easy? (350 points, SquareFind.numSquares):
This problem had some people
puzzled. I know in my room, I, and at least one other person, spent almost the
whole challenge on this problem. However, if you recognized how to determine a
square quickly, you were all set, as no special algorithm was needed to loop
through the inputs.
Given a list of points, return the number of perfect squares you can find
between all the points. Oh, and squares need not be aligned to the axes.
There were two algorithms that I saw for this problem:
1) for all possible sets of 4 points, determine if they make a square.
2) search for square line possibilities, then return the count / 4.
for the first algorithm, there were two properties that you needed to look for:
1. two diagonals were equal non-zero length
2. all four lines were equal length
If these two properties were true, it must be a square. For a good solution of
this type, see bigg_nate's solution.
For the second algorithm, this was a clever way to determine squareness.
Consider a line between two points p1 = (x1,y1)
and p2 = (x2,y2). This line is a part of a
square if and only if there exists two points p3 = (x3,y3),
and p4 = (x4,y4) in our list, such that p1,
p2, p3, and p4 make a square. If we say that
starting at pn, traveling to pn+1, we can get to pn+2
by turning left 90 degrees. Using geometry, we can calculate xn+2 by
the formula xn+1 - (yn+1 - yn), and calculate
yn+2 by the formula yn+1 + (xn+1 - xn).
Using this relationship, we can find p3 and p4.
At the end, we have each side of a square being counted once for the square
that it can be in, so we must divide the number of matches by 4 to get the
number of squares.
For a good example of this, see venco's solution
DIV-I medium (450 points, NumberFill.gradient):
This was a fill algorithm
problem with two stages. In the first stage, you searched each area for the
highest number, saving the position. In the second stage, you filled the area
from that number with the following rules when you fill in a given direction:
up, down: copy the number in the current location
right: add one to the number in the current location
left: subtract one to the number in the current location
standard recursive fill algorithms execute in plenty of time.
DIV-I hard (1050 points, Counter.tallyer):
This problem was a combinatorical problem. The range of 1 to
99999999 made sure a brute force solution would time out. Of the myriad of ways
to solve this problem, I chose two that I thought were good representations of
the mentality necessary to solve it:
METHOD 1: break the range into two more manageable pieces of 1 to 9999.
(SnapDragon)
Assume we have two functions: sum(n) = the sum of the digits of n, and prod(n)
= the product of the digits of n.
1 to 9999 is a very manageable range, and can be easily counted. The way
SnapDragon did his algorithm is:
step 1: count the ways you can make your number from 1 to 9999 for both
products and sums by using the sum and prod functions.
step 2: if high >= 10000, count the ways you can make your number from high
- (high % 10000) to high. I will explain the reasoning for this below.
step 3: Here is the tricky part. He builds a map of all the possibilities for
0000 to 9999 (including leading 0s) for both sums and products. The key of the
map is the sum or product of all the digits, and the value is the number of
ways that sum or product is achieved in this range. For each number in the
range, he adds one to the map elements with keys sum(n) and prod(n).
Then, we assume that for the range:
n * 10000 + 0 to n * 10000 + 9999
The number of "valid" numbers whose digits add up to x are the number
of numbers in the range:
0000 to 9999
whose sum adds up to x - sum(n). You can see that we can use the key x - sum(n)
to look up the precomputed value in the map. Therefore, for each range of
numbers, there is only one call to sum(n) and one search in the map.
Products are computed the same way, except instead of using x - sum(n), he uses
x / prod(n) (being careful for divide by 0 errors).
Step 3 is done for all n such that 1 <= n < high / 10000.
Notice that we do not say n <= high / 10000. The reason for this is because
we already did the values where n == high / 10000 in step 2. We cannot do those
numbers in step 3 because step 3 goes up to n * 10000 + 9999, which may be
higher than the high number.
METHOD 2: For sums, break the ranges into smaller ranges. (dmwright)
First, for the products, it is pretty apparent that all the digits must be
factors of x, and none can be 0 (unless the product is 0). This eliminates most
of the numbers in the range, allowing a brute force solution to work for
products. dmwright's algorithm to find the products is to try all values for
digits. If a particular value is not a factor of the number, or the current
product is greater than the number, or the current number is greater than the
high value, try another value.
Now, for the sums, perform the recursive function below:
for a given range, if the high limit of the range is < 10, return 1 if the
target is in the range of numbers given, 0 otherwise.
if the high limit is > 10, he sets the lowest digit to 0 - 9, and then
recurses for the higher digits by calling the function again with the range low
/ 10 to high / 10 trying to match the value x - digit. The only issue is for
the first 10 numbers in the range, and the last 10 numbers in the range. For
example, in the range 25 - 143, he actually calculates the values for 20 - 149.
He must remove the values for 20 - 24, and the values for 144 - 149. He does
this by hand calculating the sums for those numbers, and if they equal the
target, subtracting one from the total.
The final touch is to use memorization to eliminate duplicate calculations.
Note that this technique could be used for products as well, by recursing with
x / digit instead of x - digit as the new target.
By
schveiguy
TopCoder Member