Tuesday, February 14, 2006 Match summary In both Division 1 and 2, coders faced tricky problems. At the end of the coding phase, Division 1 ended with bmerry at first place followed by John Dethridge in second place. In a challenge fight these two coders managed to swap positions a couple of times. After the system tests, with four coders solving the hard correctly, the top five positions were taken by bmerry, asaveljevs (the only coder who got all three problems correctly), kalinov, rem and John Dethridge. In Division 2, only two coders solved the hard one, both newcomers, and got the first two spots, br and StanleyNguyen. King_Bette, kgd314 and aminallam followed them in a very close race. Welcome all of them to Division 1. The ProblemsObjectFallUsed as: Division Two  Level One:
The easiest way of solving this problem is to iterate from the starting yposition down to 0 and at each ycoordinate checking if the object hits some obstacle, updating its xposition and its delay. C++ code follows: int t = 0; for (int cy = y; cy>0; cy) { for (int i = 0 ; i < obstacles.size(); i++) { int y1 = atoi(obstacles[i].c_str()); int x1 = atoi(obstacles[i].c_str()+4); int x2 = atoi(obstacles[i].c_str()+8); if (cy == y1 && x >= x1 && x <= x2) { x = x2; t+=5; break; } } } return y+t; A more efficient approach is first to store each obstacle in an array of 1000 positions and then iterate from y down to 0, checking for each ycoordinate if there is an obstacle there. You can see Galeon's solution using this idea. A very detailed explanation of a oneliner solution can be found in the forums. FallingBallUsed as: Division Two  Level Two: Used as: Division One  Level One:
This problem can be solved with different approaches. Experienced coders used some flavor of dynamic programming to solve it. However, there is an easytothink solution that can be used: First of all we have to think how many paths reach each cell. The first cell, (0,0), can be reached with only one path. In the second row, both cells have only one way to be reached: the ball either falls left from the upper cell, or it falls right. In the third row, the middle cell can be accessed in two ways: either from its upperleft cell or its upperright one. In general a given cell can be reached only by the two cells above it. Then the number of ways to reach a cell is the sum of the ways of reaching those two cells. Following this reasoning we can fill the whole triangle. This triangle of numbers is known as the Pascal's triangle. Although each cell (r,c) in this triangle can be calculated with the combinatorial number: choose(r,c), the easiest way to do it is to have the whole triangle in memory and calculate each cell with the sums explained before. Let's identify each cell in this triangle as P[r][c]. The first question to answer is if the numbers in this triangle will overflow. It can be easily seen that each row of the triangle sums a power of 2, and in particular the sum of the numbers in the ith row is 2^i. Then all the numbers in the bottom row will add up to 2^29, and they fit perfectly in a C++ int or similar. But, will the number of paths overflow? There are 2^n different paths in a triangle with n rows, therefore the result number will be less than that, so we are fine. Once we have this triangle set the solution is straightforward. Let's first order the cells from the top to the bottom of the triangle. Then to move from one cell to the next one we have to do the following: let's call (deltar, deltac) to the difference between the current cell and the next one, then there are P[deltar][deltac] ways to move between them. But take into account that if (deltar,deltac) lies out of the triangle (for example, with a deltac greater than the deltar), then the second cell is unreachable from the current cell, and the answer must be 0. The previous affirmation deals not only with duplicated cells, because the deltas between them would be (0,0), but also with more than one cell in the same row or with cells far apart from each other that cannot be connected in a path. Finally the answer of the program is the multiplication of all these movements and the remaining ways of reaching the bottom line from the last cell (which is 2^(the remaining number of rows) ). C++ code follows: /// Create the Pascal's triangle and store it in P /// Parse the input and store it in an vector of pairs rc sort(rc.begin(),rc.end()); int cr = 0; int cc = 0; int ret = 1; for(int i = 0; i < rc.size(); i++) { if (rc[i].second < cc  rc[i].second > (rc[i].first  cr) + cc) return 0; ret = ret * P[rc[i].first  cr][rc[i].second  cc]; cr = rc[i].first; cc = rc[i].second; } return ret << (ncr1); For a clean implementation of this idea see Petr's solution. GreenWaveUsed as: Division Two  Level Three:
This problem is a combination of a simulation problem with a brute force search. Once we know the duration of the first red light for each traffic light, the simulation can be implemented. But in order to start a simulation, we need first to know the duration of the first red light for each traffic light. There are at most 5 traffic lights and each one can have its first red light duration up to 5 seconds. That gives 5^5 = 3125 possible combinations. If the simulation is fast enough, a brute force search on these durations can be done. A first approximation to simulate the movements could be using an array of 300 positions as the road, but this will may easily lead to time out. The simulation can be simplified using the following facts: The cars enter the road one at a time from the one with the lower index and they cannot overtake each other. Then, the cars will be always ordered in the same way within the road. At each step the cars begin to move starting with the car with the lowest index, which is still in the road, and continue to move in their index order. Using this we could do the simulation using one array for the current car's position. Note that this array will always be in decreasing order, because the ith car has always entered the road before the i+1th car it will ever be in a greater position. The simulation step goes as follows: each car tries to move one position at a time until it gets blocked by the next car or a red traffic light. When a car that has not entered the road tries to move, it checks if it can move to position 0, and this step is over. The remaining thing to see is if this simulation would time out. An upper bound of the time limit could be calculated as follows: A car cannot be delayed more than 15 seconds by the traffic lights. Then, if a car is very slow (speed 1) it will take 500 (the maximum road length) + 15 (the delay) seconds to leave the road. The state of the traffic lights will cycle every ten seconds then, at most ten seconds later, the next car will travel in the same way as the first one. The final number is 515 + 10 * (each of the remaining cars) steps of simulation. That gives a maximum of 605 seconds. In each step all the cars should be iterated given a total number of iteration as: 3125 (the different traffic lights situations) * 605 (steps of simulations) * 10 (the number of cars to iterate in each step) = 18906250, which is an acceptable number to deal with in two seconds. //generate all possible combinations of the first red light //duration for each semaphore and store it in vector delays //pos is the vector with the current position for each car //1 indicates that the car has not yet entered the road. vectorCMajor Used as: Division One  Level Two:
The number of fragments may give us a clue of what kind of solution can be used. There are too many fragments to go with all permutations, but it is small enough to check every subset. Keeping this in mind we can think on a solution. First of all, let's simplify the problem reducing each segment to its allowed initial and end notes. For example, the fragment "2 3 1", could be reduced to "G>B,C>E", meaning that it could be played either starting on a G and ending in a B, or starting from a C and ending on a E. Lets use a matrix go[F][N] to indicate where the fragment F ends if it played starting from a note N, or 1 if it cannot be used there. This can be done iterating over all the fragments, and all the possible start notes. From this point we can solve the problem using a function F(S, N) which returns the maximum number of fragments that can be appended using the fragments in the set S and starting on the note N. This function can be done trying with each fragment x in S that can be played on N (that is: go[x][N] != 1), and calling recursively with the set (Sx) and go[x][N] as the starting note for the remaining melody. This function will be called at most with 2^19 * 7 different parameters and therefore it can be memoized. Once the function is done, the answer to the problem is the maximum result of F(S,C), called with all the possible notes and the whole set of fragments. The function F can be implemented in C++ as follows, using a mask of bits to represent a subset and, of course, memoized: int F(int c, int msk) { char & r = memo[c][msk]; if (r != 1) return r; int ma = 0; for(int i = 0; i < n; i++) if (msk & (1 << i)) if (go[i][c] >=0 ) ma = max(ma,1+F(go[i][c],msk ^ (1 << i))); return r=ma; } Some coders had problems calculating the actual offset for each fragment modulus 12. There were negative jumps giving negative offsets, and it is well known that the modulus operator (%) has to be used carefully with negative numbers. For a fully dynamic programmed solution, see bmerry's one. ShrinkingPillsUsed as: Division One  Level Three:
This is a typical BFS problem, where we have to decide which elements conform the state of the search. At a first glance there are 100 (your current height) * 10 (the available pills) * 200 (the doors' height) * 48*48 (the position in the lab) possible states of search. But that is way too much states to work in time, therefore we need to reduce the search space. First of all, we need to realize that the door height can be eliminated from the state. Suppose that in the search you reach some state that has been visited before but with the doors more opened, we know for sure that you cannot improve that time with the door closer to the floor than before. Then this new state can be ignored. Another possible optimization is to replace your current height with the time elapsed since you took the last pill. This will decrease the search space for all pspeeds less than 1, and, if pspeed is 1, having more than 1 pill is never needed (proving this is left to the reader). With these changes applied, the space search is significantly reduced and a simple BFS works. Sample code follows: fine: //let v a vector of pending states //set s as the initial state int time = 0; v.push_back(s); while (v.size()) { vector 
