Archive for the ‘2009 Regionals’ Category

We considered this problem an E/F, World-Finals level of difficulty. There were 3 correct solutions – on only 7 submissions, and the first came only 65 minutes into the contest. It had the highest solution percentage of any problem outside of the two A/Bs (Knitting and Minesweeper). That means that few teams knew how to solve it, and few tried, but those who knew it, knew it well, and got it quickly and accurately.

The key to understanding this solution is to see a column of the mosaic as a bitmap – with the bit set (=1) if there’s a tile there, and unset (=0) if not. Since there are at most 10 rows, that gives us 1024 possible bitmaps, which is manageable.

Try to build a Mosaic, column by column. Since the pieces are 2×2, you can’t fill a column without leaving some residue in the next column. So, you need to figure out, for any bitmap j representing a residue from the last column, how many ways can we fill this column and leave a residue of bitmap k in the next column? The following picture illustrates one way of filling 1001101001 (617) and ending up with 1111011110 (990).

Here’s an interesting thing – that depends only on the number of rows. So, you can compute all of that a priori, before you start reading the data, rather than doing it over and over for each data set. A recursive algorithm will do the trick.

Once you’ve got that computed, then for each input, you need to fill the columns so that you start with a bitmap of 0 (on the edge, no residue), go through m columns, and end up with a bitmap of 0 (no residue – nothing hanging off the edge of the mosaic)

// Assume n rows and m columns.
// Let p be the number of bitmaps if there are n rows.
// So, p = 2^n, or 1<<n
int p = 1<<n;
// Let next[k] be the number of ways of achieving bitmap k
// on the next column
int next[] = new int[p];
// We'll start at the very beginning (a very good place to start)
// with one bitmap of 0, and no others.
next[0] = 1;
// Go through all of the columns
for( int i=0; i<m; i++ )
{
// Swap current and next - last iterations' next[]
// is this iteration's current[]
int temp[] = current;
current = next;
next = temp;
// Start afresh
Arrays.fill( next, 0 );
// Go through all possible bitmaps.
for( int j=0; j<p; j++ ) if( current[j]>0 ) for( int k=0; k<p; k++ )
{
// Assume that ways[n][j][k] is the number of ways of getting from
// bitmap j to bitmap k if there are n rows. (In the actual code, a linked list
// is used for efficiency, since this matrix will be sparse.)
//
// If there are current[j] ways of getting to bitmap j on the current column,
// and ways[n][j][k] ways of getting from there to bitmap k, then we've
// just found current[j]*ways[n][j][k] more ways of getting to bitmap k
// on the next column. So, add them in.
next[k] += current[j] * ways[n][j][k];
// Mod to keep the numbers manageable.
next[k] %= 1000000;
}
}
// We're done! How many ways were there of achieving bitmap 0
// on the last iteration - that is, we nicely filled in the mosaic with
// nothing hanging over?
System.out.println( next[0] );

We had wanted this problem to be a Dynamic Programming problem, and early versions of it had large input limits that would force DP. However, other constraints caused us to put limits on it, making it solvable by other means. It was in our “second wave” of problems. The “first wave” was Knitting and Minesweeper – every team that solved one or two problems solved one (or both) of those two. This problem and “Euclid” made up the second wave – every team that solved 3 or 4 solved both problems in the first wave, and then one or two from the second wave. Every team that solved more than 4 solved both problems in the first wave, and both problems in the second wave.

The original intent was for this problem to require Dynamic Programming. Create an array best[], where best[i] is the best you can do from here to the end if you stop at target point i. Clearly, best[i] can be computed pretty simply from best[i+1], best[i+2], and so on (best[i] = Minimum over k of [1+(the cost of skipping targets between i and i+k) + (the Euclidean distance between i and i+k) + best[i+k]]. ). So, start at the end, work back to the beginning, and best[0] is your answer.

The numbers were small enough, however, that a simple, best-first-search shortest path (aka Dijkstra’s Algorithm) would work. Define the “Distance” between two targets as 1 + (the Euclidean distance) + (the cost of skipping targets in between). Then, just run Dijkstra’s Algorithm and voila.

Note: in both cases, the “1+” is for the one second that the robot must stop on a target point.

For the first part, finding the distance, it’s easier to not try to compute reflections, but rather to set up a coordinate system of reflected pool tables. Let dx and dy be a number of pool tables over, and up. If n is the desired number of bounces, then look at all reflections where |dx|+|dy|=n.

For example, if n=2 bounces, instead of this:

See it as this:

Then, look at all reflections where |dx|+|dy|=n (for our example of n=2, they’re the lightest colored ones in the example above), and just calculate the straight linear distance between the cue ball and the reflection of the target ball. Be careful about the position of the target ball in the reflection!

Then, the second part of the problem is to make sure that there are no reflections of the target ball that get in the way – that is, no chance of hitting the target ball too soon. You’ve got to check that no reflection of the target ball (including the original!) is collinear with the cue ball and the reflection you’re trying to hit AND between them.

Here’s a case that caught a few teams: consider a large table, with the cue at (1,1) and the target at (2,2), with n=2 bounces. The correct answer is to shoot at the corner at (0,0), and have the cue bounce straight back to the target. Several teams failed this particular test. It’s because, in checking to see if an earlier version of the target blocked the shot, they only checked collinearity, not betweenness. Here, the original target ball is collinear with the cue and the reflected target, but it is not between them!

This problem turned out to be harder than we expected. We rated it a C/D, yet only 2 teams managed to solve it, the first coming about three and a half hours into the contest. It’s really not a difficult algorithm, but it’s off the beaten path, and requires some thinking and some care.

Use a Greedy algorithm. First, assume that the trees are laid out right next to each other, as tightly as possible. Then, find the positions of the smallest and largest trees – let’s say s and t – and then try to stretch the gap between s and s+1 as far as possible, then the gap between s+1 and s+2, then s+2 and s+3, all the way up to t-1 and t. Look at all pairs of trees that are adjacent height-wise (that the Ninjas would jump between) that span each of those gaps, to see how far the gaps can be stretched.

The only way the Greedy algorithm can fail is if stretching an early gap limits your options of stretching a later gap. I’m not going to go through a convoluted argument, but it’s true. It’s left as an exercise to the reader to confirm!

The algorithm is obvious – just do what the problem says. For every non-mine cell, look at the (up to) 8 cells around, and count the number of mines. So, the key to solving this problem is just to be careful about certain things.

Don’t go off the edge of the board when you’re checking edge cells and corner cells

For some, correctly convert an integer to a char (others just printed the int directly). In both C++ and Java, this will work: ch = (char)(x+’0′)

We’ll frame this as a Max Flow problem, but with a twist. We’ll start with a brief discussion of Max Flow problems.

Consider a directional, weighted graph that has a special node called the ‘Source’, and another called the ‘Sink’. Consider the weights to be a ‘capacity’. Think of each edge like a one-way pipe, and the ‘capacity’ is the most stuff you can push through that pipe. (The internet is a series of tubes, right?) Well, the question is… How much stuff can you push from the Source to the Sink, given the limited capacities of all the edges?

The solution technique for a Max Flow problem is called Ford-Fulkerson, and I’ll only give a brief outline of it here.

First, for every edge A⇒B, create a dual edge, B⇒A, which starts with 0 capacity. The way to think of A⇒B and its capacity is: How much capacity is available to use. The way to think of B⇒A and its capacity is: How much capacity has been used, that I could give back if I had to. That’s why B⇒A starts out as 0 – because, at the beginning of the algorithm, you haven’t done anything, so you haven’t used any capacity.

Now, find a path from the Source to the Sink, only traversing edges that have some capacity (that is, capacity>0), and don’t hit any node more than once. You can find this path any way you like, but Breadth-First Search has become the standard.

When you’ve got a path, go back over the edges on the path, and find the one with the smallest capacity. That’s the most stuff you can push through that path, right? That’s the weakest link. Then, go back over the path again, and move that amount of capacity from each edge to its dual (that is, subtract it from each edge, add it to each edge’s dual).

Keep doing this (finding paths, finding the min capacity, moving that capacity from each edge to its dual) until you can’t find a path with >0 capacity. Then, you’re done! The total amount of capacity that you moved over all the paths you found is the Max Flow!

OK, so how do we see Museum Guards as a Max Flow problem? Well, the ‘stuff’ we’ll flow through the system is guard-periods. Build two kinds of nodes, for guards, and for time periods. Link the Source to each guard, with the capacity being the number of time periods that guard is able to work. Link a guard to a time period if the guard is willing to work that time period, with capacity 1. Finally, link the time periods to the sink. What should the capacity for those edges be? Well, that’s tough. We could set them all to n, and see how many guard-periods flow through – but we have no way of forcing the guard-periods to be balanced over the time periods. Some periods could be over-represented, and some under-represented. The result wouldn’t be meaningful. Hmmm….

Consider this: If we set the capacity of each of those links to some value x, 0≤x≤n, and we get a total flow of x*48 (48=number of 30 minute periods in a day), then we know we can cover all time periods with x guards. The only way to geta total flow of x*48 is to get a full x from each of the 48 time periods. We can’t get any more from one time period (and less from another), because we’ve set the max capacity from each time period to the sink to x. That’s our hook. We just have to do repeated Ford-Fulkersons to find the largest value of x which leads to success.

This one was just Math. Some teams solved this very quickly, others struggled with it. It was still one of the 4 most solved problems. There were several ways to approach it, which you’ll see in the spoiler.

I’ll talk about three different solutions, but all of them have one thing in common. They all express points G and H, (and, more specifically, their coordinates Gx, Gy, Hx and Hy) parametrically in terms of a parameter t. This parameter represents how far along ray AC point H is. If t=0, then H is right at A. If t=1, then H is right at C. If 0<t<1, H is between A and C. If t>1, then H is further away from A than C is. Once we know t, we can figure out all the coordinates like this:

The first solution, in euclid.java, is a closed-form solution using more trig than anything else. It uses Heron’s formula to get the area of triangle DEF. It then uses the Law of Cosines to get the cosine of angle CAB. Using the distance of line segment AB (we’ll notate that |AB|), and our target area, we can compute the height of the desired parallelogram as area/|AB|. Since we know the cosine of CAB, we can easily compute the sine, and then use the Law of Sines to get the distance we need along ray AC. Then, t = distance / |AC|. The rest is as above.

The second solution, in euclid_binary.java, uses an age-old trick to get the area of any polygon. For every edge (x1,y1) to (x2,y2), add up (y2+y1)*(x2-x1). The area of the polygon is one half of the absolute value of that. Or,

area = 0.5*|∑(y2+y1)*(x2-x1)|

It uses that trick to get the area of triangle DEF. It then uses that trick to get the area of a parallelogram given parameter t, and uses Binary Search to nail down the correct value of t.

The third solution, in euclid_chinmay.java, uses the fact that the magnitude of the cross product of two vectors (with the same base) gives the area of the paralellogram defined by those two vectors. Also, half of that would be the area of the triangle defined by those two vectors. So, the area of triangle DEF is 0.5*|DExDF|. The area of a parallelogram defined by CAB is |ACxAB|. Then, just let t = 0.5*|DExDF| / |ACxAB|, and we have our answer. This works because the area of the parallelogram we seek will be linearly related to the distance that H is from A along AC.

This problem had a fairly standard solution, but there was some tricky bookkeeping, which is probably why so few teams solved it. It particular, there were two issues that caught teams.

First, a lot of teams didn’t reset the board correctly when moving pieces. For example, suppose that both pieces A and B can move. Some teams wrote programs that would move piece A all the ways it could move, but would forget to put it back where it started before trying to move piece B.

The other catch was a little more tricky. A lot of teams wrote code that checked to see if the special piece could be moved all the way to the right. They figured that if you could move it there, you could just keep moving it and slide it out. And, most of the time, they would be right – but what if the special piece starts out all the way on the right side of the board? A lot of teams would answer 0 – that is, 0 moves. That’s incorrect. It always takes at least 1 move to remove the special piece.

It’s just a breadth-first search. Despite the small size of the board and the limitations on how pieces move, the search space can still get quite large, so if you tried to use Depth-First Search (that is, you tried to solve it recursively) you probably ran out of time.

Create a queue of states. Each state has a board, and a number of moves. Start by putting a state on the queue that has the starting position, with 0 moves.

Each time you take a state off of the queue, check to see if the special piece is all the way to the right. If so, remember the number of moves, and exit the loop. Otherwise, generate all possible boards that can be attained in 1 move. For each, check to see if it’s been seen before, and if not, put it on the queue with a number of moves equal to this state’s number of moves plus one.

When you’re done, output the number of moves remembered, unless it’s 0, in which case output 1, or if you emptied the queue without reaching a solved state, output -1.

Sounds simple in paragraph form, but it’s not necessarily easy to generate all of the possible moves – remember, a lot of teams didn’t reset the board correctly. Also, remembering previously seen board positions is important, otherwise you’ll run out of time and memory.

Welcome!

This blog is about the Southeast USA Regional Programming Contest, and in particular, Problems and Problem Sets, and Judging at the Contest. We can discuss other things about the contest, but these are the two that I have control over. If you'd like to suggest a topic, leave a comment on the ABOUT page, and if I like it, I'll post an article for discussion