JOIN
 Match Editorial
2003 TopCoder Collegiate Challenge
Regional SemiFinals

Wednesday, March 5, 2003

Summary

The regional semifinals caused some huge upsets when top seeded John Dethridge was knocked out, together with other high ranked coders such as reid and malpt (reid did not show up at all). The cutoffs for the different regions differed quite a lot; John Dethridge would for instance have qualified had he competed in any other region. Top scorers were bigg_nate and Yarin. The problem set was arguably one of the better in this CC, with a mix of math, dynamic programming and geometry.

# The Problems

Ordered
Used as: Level 1:
 Value 200 Submission Rate 127 / 129 (98.45%) Success Rate 111 / 127 (87.40%) High Score bigg_nate for 193.50 points

Reference implementation: Yarin (room 2)

#### Implementation

I suspect an easy (200 pts) first problem was selected just to make sure all regional finals would have 10 competitors... Still, the problem had some pitfalls, the major one being spelling errors, even though the notes warned about that.

Given a sequence of numbers, we should deduce if it's strictly increasing, strictly decreasing, non-decreasing or non-increasing (which I believe are the proper terms). To check the first two cases, just make sure every value is greater (smaller) than the previous value. If this is not the case, set a flag. We do the same for the other two cases, but instead check that every value is greater-or-equal (smaller-or-equal) to the previous value.

Depending on the results, we should either append the mean of the values (reduced fraction, so we need to use GCD) to the result string or the frequency of the most common element. It could also be that none of the four types of sequences applied, in which case we simply returned "NOTHING".

GreedyChange
Used as: Level 2:
 Value 500 Submission Rate 73 / 129 (56.59%) Success Rate 46 / 73 (63.01%) High Score bigg_nate for 444.78 points

Reference implementation: bigg_nate (room 13)

#### Implementation

This is a typical DP (dynamic programming) problem. It consists of two parts - first we calculate the optimal way of handing back coins for all values up to a limit. Then for each such value we compare with the greedy method. If the greedy method is worse than the optimal, we return this value. Otherwise we keep going until we hit the limit (see below), and once we hit it, we return -1.

To calculate the optimal way of handing back coins, we use induction. Assume we know the fewest coins possible for all integers x<k (this value is stored in a big array, opt[], where opt[x] = the fewest coins possible when the value is x). Now we should calculate the fewest coins possible when the value is k. The idea is that for any value k>0, we must first give back one coin. After that, the remaining value is less than k, and for that value we already know the fewest coins possible (by induction). So, we loop through all denominations (call them d[i]), check opt[k-d[i]], and pick the minimum of these values. Thus,

`opt[k]: for all denominations d[i]: min(opt[k-d[i]])+1`

For the greedy approach, we can use induction again. Assume we know that the greedy method equals the optimal method for all integers x<k. That is, opt[x]==greedy[x]. Now, in order for greedy to work on the value k, opt[k-d_max]+1 must equal opt[k], where d_max is the highest denomination less than or equal to k. The reason for this is that this is the coin we will hand back first; after that we know by induction that greedy works for the value k-d_max, so the number of coins that we hand back by this method is opt[k-d_max]+1.

In order for the program to run in time, we need a quick way to find out the largest coin less than or equal to the current value. This can be solved nicely by first sorting the coins, and then as we loop through the values, we keep updating which coin is currently the largest less than or equal to the value - the reference implementation handles this nicely.

This is all fairly standard stuff. What maybe made this problem slightly harder is to figure out how long you should search before deducting that greedy works on all values. I believe most people simply guessed (I know I did), with the hint from the last example case, that if greedy worked for all values up to 2N (where N was the largest denomination), it worked for all values. Below follows a proof that this is actually the case:

Assume greedy works for all numbers less than k, k>2N, where N is the largest coin. We will now prove that greedy works for k as well. By assumption, we have the true recurrence that opt[k]=min(opt[k-j]+1) where j is the value of some coin. Now, we know that the greedy solution works for opt[k-j] and also that k-j>N. Thus, opt[k-j]=opt[k-j-N]+1. So, we can rewrite this as:

```                                        opt[k]=min(opt[k-N-j]+1)+1
```

since the greedy solution for k-j involves using a coin valued at N (k-j>N). Now, we can rewrite min(opt[k-N-j]+1) as opt[k-N], from our original recurrence, and thus we get:

```                                        opt[k]=opt[k-N]+1
```

which is what the greedy solution gives. QED (proof by lbackstrom)

SolidArea
Used as: Level 3:
 Value 1000 Submission Rate 49 / 129 (37.98%) Success Rate 39 / 49 (79.59%) High Score Yarin for 824.44 points

Reference implementation: Yarin (room 2)

#### Implementation

Given a polygon on the xy-plane, the polygon is moved "up" along the z-axis while being enlarged. This creates a solid, see picture below. The problem is to calculate the area of all the surfaces of this solid.

The solid has two kinds of surfaces: the two polygonal surfaces (top & bottom), and the n faces. The problem constraint guarantees that the solid will be "nice", no crossing surfaces or any such (in fact, I don't think such a figure would be called a solid!).

The area of the two polygonal surfaces can be calculated with a formula that, if you don't know it, you should learn!

PAREA = ((x1*y2-x2*y1) + (x2*y3-x3*y2) + .... + (xn*y1-x1*yn)) /2

where the polygon points are (x1,y1), (x2,y2), ... , (xn,yn). Depending on whether or not the polygon points are clockwise or counter clockwise, you may have to negate the value above.

It remains to calculate the area of the n sides. Each side is a trapezoid with coordinates in 3D. The coordinates for the trapezoid are (xi,yi,0),(xi+1,yi+1,0),(xi*f,yi*f,s),(xi+1*f,yi+1*f,s) where xi,yi are the original coordinates for the polygon, f is the enlargement factor and s is the shift value (how much the polygon is moved up along the z-axis).

Now, calculating the area of a trapezoid is fairly simple - multiply the average base length with the height. The problem here is that the trapezoid is in 3D and calculating the height requires some elementary knowledge in linear algebra. Instead I went for a different approach: I divided the trapezoid into two triangles and calculated the area of those using Herons formula:

TAREA = sqrt(p*(p-a)*(p-b)*(p-c))

where a, b, c are the sides of the triangle (calculated from the coordinates using Pythagoras formula) and p is half the perimeter, (a+b+c)/2.

That's basically it. The answer is sum of all these areas, rounded down. Check the reference implementation for details.

By Yarin
TopCoder Member