Wednesday, November 14, 2018

A plus four week

Codeforces hosted two rounds during the Aug 27 - Sep 2 week. AIM Tech Round 5 took place on Monday (problems, results, top 5 on the left, analysis). All problems were solvable this time for LHiC and OO0OOO00O0OOO0O00OOO0OO, but Mikhail was considerably faster of the two. Congratulations on the win!

Manthan, Codefest 18 was the second Codeforces round of the week (problems, results, top 5 on the left, analysis). The contest really came down to the wire, with the top three participants all completing the problem set with just a few minutes to go. Tourist was just a tiny bit faster with the easier problems, and thus earned the victory. Well done!

This week also marked the end of the summer Petrozavodsk training camp (results, top 5 on the left), the 9-contest event for top Russian and some other ICPC teams to practice before the new season. Given that the second-placed team in those standings is not actually going to participate in official ICPC contests this year, the gap between the first team (current ICPC World Champions) and the rest of the field is daunting, even though it's only August yet :)

In my previous summary, I have mentioned a TopCoder problem: you are given a 10x10 grid and can place at most 150 unit cubes onto it. A cube can be placed onto a cell of the grid, or on top of another cube. Given a number s between 1 and 500, you need to place the cubes in such a way that the surface area of the resulting figure (the total number of sides of the cubes that do not touch the grid or other cubes) is equal to s, or report that it's impossible.

The approach I will describe is a very typical one for such "constructive" problems. Suppose we have found the solution for some value of s. Let's take one extra cube and put it on top of an existing one. This will increase the surface by four (five new sides appear, and one old one is covered), so we'd get a solution for s+4. We can now apply the same trick to get a solution for s+8, s+12 and so on.

Since we spend one cube to increase the surface by 4, and 150*4 is significantly bigger than 500, we won't run out of cubes.

Now the only task that remains is to find out the smallest possible figure for each remainder of s modulo 4. This can be done by analyzing a few cases by hand: it turns out the minimum solvable s for each remainder are 8, 5, 10 and 11.

During the round I've initially discovered another induction idea: just placing a new cube on the grid in such a way that it's disconnected from the rest adds 5 to the surface, so I've tried to build a similar solution modulo 5. However, in that case we do run out of space as we can have at most 50 independent cubes on the 10x10 grid, and 50*5 is less than 500.

Thanks for reading, and check back for more!

Tuesday, November 13, 2018

A 250+ week

The Aug 20 - Aug 26 week was very calm compared to the previous ones, with just the TopCoder Open 2018 Online Wildcard Round on Saturday (problems, results, top 5 on the left, parallel round results, analysis). ACRush, Egor and Stonefeang were the only participants to solve all three problems, but ACRush and Egor were almost twice as fast in solving the hardest one, thus qualifying to the TCO onsite with a 250+ point margin. Well done!

The easy problem in this round was cute. You are given a 10x10 grid and can place at most 150 unit cubes onto it. A cube can be placed onto a cell of the grid, or on top of another cube. Given a number s between 1 and 500, you need to place the cubes in such a way that the surface area of the resulting figure (the total number of sides of the cubes that do not touch the grid or other cubes) is equal to s, or report that it's impossible.

Thanks for reading, and check back for more!

A birdie week

TopCoder SRM 736 started the Aug 13 - Aug 19 week (problems, results, top 5 on the left, my screencast, analysis). This was the first round under the new system in which one can qualify for the last online round and even to the onsite round of TopCoder Open 2019 by placing well in all SRMs in a quarter. rng_58 has claimed the early lead in that leaderboard by winning the SRM by less than one full point!

Codeforces then held two rounds based on VK Cup Finals problems, starting with Round 504 on Friday (problems, results, top 5 on the left, analysis). Just as in the official round, the hardest problem remained unsolved, but this time the winner was determined on challenges. ko_osaga found 9 opportunities and got a clear first place as the result. Well done!

Facebook Hacker Cup Round 3 on Saturday selected the 25 lucky finalists going to Menlo Park (problems, results, top 5 on the left, analysis). Xiao was quite a bit faster than the rest of the pack, qualifying to the onsite finals in style. Congratulations to Xiao and to all finalists!

Finally, another VK Cup-based Codeforces Round 505 wrapped up the week (problems, results, top 5 on the left, analysis). Once again the hardest problem remained unsolved, and once again it took solving all remaining problems plus finding a few challenges to earn a victory — and Swistakk did just that.

Thanks for reading, and check back for more!

A Toronto week

The Aug 6 - Aug 12 week was the Google Code Jam final week, onsite in Toronto. Distributed Code Jam 2018 Finals has opened the event on Thursday (problems, results, top 5 on the left, analysis). The contestants pursued wildly varying sets of problems, but in the end only Radewoosh, kevinsogo, tczajka and fagu could solve more than one full problem. Congratulations to all four, and especially to Radewoosh on the win!

The more traditional Code Jam 2018 Finals followed a day later (problems, results, top 5 on the left, analysis). Once again the sets of problems of the top contestants were very different, but this time only one participant managed to get three full problems: Gennady.Korotkevich. Congratulations on the victory!

Codeforces Round 503 took place on Saturday (problems, results, top 5 on the left, analysis). This time it was top four participants on four problems, with Marcin_smu barely claiming the first plce thanks to having only one pretests failure compared to Radewoosh's five. Well done!

Finally, VK Cup 2018 Final onsite in St Petersburg wrapped up the week on Sunday (problems, results, top 5 on the left). The heavy pre-round favorite "Нижний Магазин SU: BZ" was a lot faster than everybody else to solve the first five problems, but the system test failure meant that they had to settle for third. Team "120 Minutes Adventure" was more careful and thus claimed the victory. Congratulations!

Thanks for reading, and check back for more!

Friday, November 2, 2018

A unit week

The Jul 30 - Aug 5 competitive week started early with Codeforces Round 500 on Monday (problems, results, top 5 on the left, analysis). There were a few strategy options on the table, as the last two problems had the same point value and some people went for E and some for F: E turned out to be the right choice. Congratulations to Um_nik on the win!

Facebook Hacker Cup 2018 Round 2 followed on Saturday (problems, results, top 5 on the left, analysis, my screencast). Getting into the top 200 was the main goal, but there was some competition for the top spots as well, where Alex was a bit faster than everybody else. Well done!

In my previous summary, you are given a program for a drawing robot consisting of at most 250000 commands. Each command is either F "move forward by 1 unit, drawing a line", L "turn left by 90 degrees", or R "turn right by 90 degrees". The drawn polyline splits the plane into multiple regions, some finite, some infinite. Your goal is to return the list of the areas of all finite regions.

There's a somewhat well-known approach to the general problem of finding faces of a planar graph which is described in the official analysis. However, I've used the specifics of the problem and went with a different approach that I found less bug-prone.

Let's imagine the plane as a grid, split the polyline into unit segments, and look at the vertical unit segments for now. Every row of the plane will be split into two infinite blocks plus some finite k times 1 blocks by the vertical unit segments. The overall number of finite blocks is at most 250000.

Now let's put those blocks into a disjoint-set data structure, and merge the blocks that are adjacent vertically. In order to find the latter, we need to iterate over pairs of adjacent rows with two pointers, and not forget to take the horizontal polyline unit segments between those rows into account.

While the general planar graph algorithm is not harder than this one, I found that this approach allows to sidestep all corner cases, for example: what if there is a vertex of degree 1? What if there's more than one connected component of the polyline (not possible in this problem)?

Thanks for reading, and check back for more!

Wednesday, October 31, 2018

A week of 14

The Jul 23 - Jul 29 week warmed up with Codeforces Round 499 on Thursday (problems, results, top 5 on the left, my screencast, analysis). Without taking the challenges into account the top of the scoreboard would be quite packed, but the challenges are of course taken into account, skyrocketing Kostroma and cki86201 to clear first two places. Well done!

The main event of the week took place on Saturday, with TopCoder Open 2018 Round 4 selecting 14 finalists that would go to Texas (problems, results, top 14 on the left, parallel round results, my screencast, analysis). Both harder problems were approachable, but nobody was able to get all three, resulting in a fairly diverse scoreboard. Kankuro was still considerably faster than everybody else,
and has also cemented his lead with a successful challenge. Congratulations to Vladislav and to everybody who qualified for the onsite round!

I found the medium problem in this round quite educational, if a bit standard. You are given a program for a drawing robot consisting of at most 250000 commands. Each command is either F  "move forward by 1 unit, drawing a line", L "turn left by 90 degrees", or R "turn right by 90 degrees". The drawn polyline splits the plane into multiple regions, some finite, some infinite. Your goal is to return the list of the areas of all finite regions.

Thanks for reading, and check back soon for the solution!

Monday, October 29, 2018

A decision tree week

The Jul 16 - Jul 22 week provided the second and last chance to progress to the round of 100 in the TopCoder Open 2018, with 50 more advancers chosen in Round 3B (problems, results, top 5 on the left, parallel round resultsanalysis). qwerty787788 was the only one to solve all three problems correctly, and thus won the first place with quite a margin — congratulations to Borys and to everybody who advanced!

Facebook Hacker Cup 2018 Round 1 took place over the weekend of that week (problems, results, top 5 on the left, analysis). Being fast was not a requirement this time, as the penalty time did not factor into advancement, but of course a competition is always a competition :) Congratulations to Kevin (are you ksun48?..) on the win!

In my previous summary, I have mentioned a very nice AtCoder problem: two players play a game on an array of integers ai with at most 300000 elements. The players make alternating turns, and on each turn a player takes a number from the array that is not previously taken. Moreover, when possible, a player must take a number adjacent to a number taken by the other player on the last turn. When taking such adjacent number is not possible (on first turn, or after a player has taken a number which has both neighbors either taken or nonexistent), any number can be taken. The game ends when all numbers have been taken, and each player tries to maximize the sum of the numbers they take. Which sum will each player get if both play optimally?

Let's color all numbers with alternating colors, first one is red, second one is blue, third one is red, and so on. First, suppose the total amount of numbers is even, in other words the last number is blue. The first player has a few options:
  • Take the first number. Then the rest of the moves is uniquely determined, they will take all numbers from first to last, the first player taking all red numbers, and the second player taking all blue numbers.
  • Take the last number. In this case everything is determined as well, but the first player gets all blue numbers, and the second player gets all red numbers.
  • Take a number in the middle. In this case the second player has choices as well, so let's study this case more carefully.
Suppose the first player starts by picking a red number that is not the first number. The second player has two choices now: go left or right. If they go left, they will collect all blue numbers to the left, and the first player will collect all red numbers. Since the first number is red, the first player will be the last to move in this chain, and the second player will be able to pick any number. First key observation is that they can then take the last number, and thus collect all remaining blue numbers. So if the first player picks a red number, the second player can always take all blue numbers.

Similarly, if the first player starts by picking a blue number, the second player can take all red numbers. So the second player can always guarantee at least the minimum of the two sums: all red numbers or all blue numbers. However, the first player can force the second player to get at most that minimum (by taking the first or the last number). Hence with optimal play the second player will get exactly that minimum (and thus the first player will get the maximum of the two sums).

Note how we didn't have to explore all possibilities here — for example we didn't consider the case where the first player starts with a red number and the second player moves to the right. We don't need to: by finding an upper bound and a lower bound for the second player's score that match, we have proven that other options will not change the answer. This observation will also be key to solving the harder case, where the overall amount of numbers is odd.

When the overall amount of numbers is odd, both first and last numbers are red, and the first player can still take all red numbers by taking the first number. However, there's no way for the first player to do the opposite and take all blue numbers. Let's again consider the other options the first player has:
  • Take a red number that is not the first or last. Then the second player can still collect all blue numbers as in the even case, and since the first player can force the second player to take all blue numbers, we have matching upper and lower bounds again, and we don't need to investigate other second player's strategies in this branch.
  • Take a blue number. In this case the second player chooses a side, and takes all red numbers on that side, and then the first player is again first to play on the other (remaining) side, which once again has an odd amount of numbers.
The first player can continue picking blue numbers when they have a choice for some time, until they decide to pick a red number in some remaining segment. Then the second player will have gotten all red numbers outside this remaining segment, and all blue numbers inside this segment.

Can the first player choose this segment arbitrarily? No, since the second player decides whether we go left or right every time the first player picks a blue number. In fact, we can imagine the optimal strategy for the first player like a decision tree: we start by picking a certain blue number. If the second player takes all red numbers to the left, we are left with the right part, and will pick a certain blue number there; if we have the left part, we will pick a certain blue number there, and so on. Ultimately, each branch ends with us (first player) picking all red numbers in the segment.

Now we can notice that the decision tree structure actually does not matter much. The blue numbers used for branching in the decision tree split the other numbers into segments. In the end, the second player gets all blue numbers from one of those segments, and all red numbers from the rest. The first player can split all numbers into such segments arbitrarily, and the second player effectively gets to choose the segment it gets blue numbers from.

Note that we did not find an arbitrary strategy for both players — we have shown that the optimal strategy for them must look like this. The only remaining question is: how should the first player split all numbers into segments?

This is another cool side of the problem: after solving the "more mathematical" strategy part, the remaining "algorithmic" part is also interesting to solve! We have an array of numbers of odd length, with first, third, ... numbers colored red and the rest colored blue. We need to choose a set of blue numbers in such a way that minimizes the maximum of (sum of blue numbers minus sum of red numbers) over the segments between the chosen blue numbers.

There's a straightforward quadratic dynamic programming solution which finds the optimal way to split each prefix of our array, and iterates over all possible positions of the last taken blue number in each prefix.

In order to speed it up, we have to slow it down first. Let's add an outside binary search for the answer. Now instead of minimizing the maximum, we just need to check if there's a split such that on each segment the difference is below the threshold. This can still be done using a similar dynamic programming, for the overall runtime of O(n2*logn).

But now we can notice that in this simplified dynamic programming if position X of the last taken blue number is better than position Y for some prefix, it will be better for all bigger prefixes as well, since what happened before X/Y does not matter anymore (it just needs to be under the threshold), and the only thing that matters is the difference between blue and red numbers after X/Y. Whichever is smaller once will keep being smaller, since we add the same new numbers to both. So instead of trying all possible positions for the last taken blue number, we can try just two possibilities: the best position for the previous prefix, or just the previous blue number (which was not considered for the previous prefix because it was not there). This makes the solution run in overall O(n*logn) time.

Here is my entire solution from the contest:

int n = in.nextInt();
int[] a = new int[n];
for (int i = 0; i < n; ++i) a[i] = in.nextInt();
int s1 = 0;
int s2 = 0;
for (int i = 0; i < n; ++i) {
    if (i % 2 == 0)
        s1 += a[i];
    else
        s2 += a[i];
}
if (n % 2 == 0) {
    out.println(Math.max(s1, s2) + " " + Math.min(s1, s2));
    return;
}
int left = s1 - s2;
int right = s1 + s2 + 1;
outer: while (right - left > 1) {
    int middle = (left + right) / 2;
    long bestSoFar = s1 - s2;
    int r1 = s1;
    int r2 = s2;
    for (int i = 1; i <= n; i += 2) {
        r1 -= a[i - 1];
        long cost = bestSoFar - (r1 - r2);
        if (i < n) r2 -= a[i];
        if (cost >= middle) {
            if (i == n) {
                left = middle;
                continue outer;
            } else {
                bestSoFar = Math.max(bestSoFar, r1 - r2);
            }
        }
    }
    right = middle;
}
out.println((s2 + left) + " " + (s1 - left));

Thanks for reading, and check back for more!