Showing posts with label Code Jam 2014. Show all posts
Showing posts with label Code Jam 2014. Show all posts

Sunday, June 1, 2014

GCJ 2014 Round 2 Follow-up

Yesterday I left the post with 2 GCJ14 Round 2 unsolved problems. Here are the solutions to those problems

Problem C: Don't Break The Nile: as I said, we can use Maximum flow algorithm to solve this problem, but actually we don't have to run the full straightforward MaxFlow algorithm. We can apply the idea that MaxFlow = Min-cut to solve this problem. A way that we can find the min-cut to the graph is to draw the min-cut curve going from the west to the east side. Consider the following picture (from GCJ2014 sample test):

The purple line is a minimum cut on this graph (the network flow graph is not shown). Basically, we try to make a new graph such that all builds are represented as vertices, and there are 2 more vertices for the west and east sides. We added an edge between each pair of vertices (For simplicity, some edges are not shown in the picture). The weight of each edge is the distance between two buildings. In analogy, the cut's cost between two buildings is the amount of water that can go through the gaps between the buildings. So let's think a bit of how we can find the minimum cut? --------- Look like a shortest path problem, right? and yes, the cost of the minimum cut is just the shortest path from the west to the east side. That's it. The size of the entire grid doesn't matter here. I really like this problem!

About implementation, finding the distance between two buildings are somewhat tricky though. My method is like there are four points on each rectangle. Let's take the minimum distance between the 16 pairs of points between the two buildings (The distance between two points here is max(abs(p1.x - p2.x), abs(p1.y - p2.y)) - 1). This is not all. We have to consider 4 more cases where two buildings are located side-by-side :)

Now move to the hardest problem: Problem D. Trie Sharding. There are two parts of this problem: 1) Find the maximum total number of nodes of all possible group arrangements. 2) How many group arrangements that results in that maximum number of nodes. To answer the first question, let's build a trie of all the strings. Consider the following picture for the first sample input ("AAA", "AAB", "AB", "B"):

The original trie can be composed of these two tries ({"AAA", "B"}, {"AAB", "AB"}). Considering the original trie, the second number on each node indicates how many times this node can appear on different tries (Of course, the number can't be greater than N). The way that we can calculate all these numbers is to starting from leaves to the root. We can be sure that each node that represents the end of a string can appear only once on a trie, so we can set the second number to 1. For the other nodes, their second number are the sum of the second numbers of all of their children. If the sum is greater than N, set it to N. Finally, the sum of all the second numbers are the total number of the nodes. Answering the second question is harder. In different interpretation, the second number is actually the number of trie subtrees rooted at each node. We claim that that the number of group arrangements is the multiplication of the number of ways to make each subtree. Consider the root subtree as an example. There are 2 subtrees from the first child and 1 subtree from the second child. We have to count the number of ways to combine these 3 subtrees into 2 tries. So the subproblem is that given a list of numbers x(#subtrees on each child), and a number kk = min(N, total #subtree), count how many ways we can combine all the subtrees into kk tries (trees). The order doesn't matter and two subtrees on the same child can't be in the same resulting subtree.

For example, x = {2, 1}, kk = 2, there are 2 ways: ({X, Y}, {X}) and ({X}, {X, Y}), X is a subtree from the first child and Y is a subtree from the second child. Notice that two x's need to be in the different trees.

This can be solved by Dynamic Programming:

long long count(vector<int>&x, int kk) {
    // dp[i] = the number of ways to combine x into i tries
    // C[i][j] = (i choose j)
    int sz = x.size();
    for (int i = 1; i <= kk; i++) {
        dp[i] = 1;
        for (int j = 0; j < sz; j++)
            dp[i] = (dp[i] * C[i][x[j]]) % MOD; // there are i tries, choose x[j] tries for x[j] subtrees 
        for (int j = 1; j < i; j++)
            // we subtract dp[i] by the number of ways that we don't use all i tries.
            dp[i] = (((dp[i] - dp[j] * C[i][j]) % MOD) + MOD) % MOD;
    }
    return dp[kk];
}
The end!!!

Saturday, May 31, 2014

Google Code Jam 2014 | Round 2

Today GCJ 2014 Round 2 contains 4 problems. The top 500 contestants will advance to Round 3. In this round, at least 50 points with small penalty time are required to pass this round. And yeah! I got 413rd ranking. This was my first time that I got a GCJ t-shirt and advanced to Round 3.

let's talk about the problems. The first problem (A: Data Packing): given N files with their capacities and disc capacity, what is the minimum number of discs are required to store all the files with 2 conditions:

  1. A disc can contain only at most 2 files.
  2. Each file can't be divided to stored separately in different discs.
This problem was the easiest one of this round. An O(nlogn) solution can get accepted. My greedy solution is to loop from the smallest file a and for each of them, find the biggest file that can be put into a disc along with this file a. If we can't find one, just put a into a disc. We repeat until no files left. A nice data structure for doing this is multiset (C++), because we can call lower_bound() and there might be duplicate file capacities.

The second problem (B: Up and Down): give a list of integers, we would like to rearrange the sequence to an up and down sequence (one where A1 < A2 < A3 <...< Am > Am-1 >...> An for some index m). We can rearrange them by swapping two adjacent elements of the sequence. The problem asked you to find the minimum number of swaps needed to accomplish this. First my first attempt to solve this problem was to find where we should place Am (it's obvious that Am is the largest element of the sequence). There are n possible positions. We can try moving Am to each position, then find the number of inversions of the left part and right part of the sequence. This idea was completely wrong. We can't prove anything that doing this will give us the minimum number of swaps. After thinking for a while, I came up with a solution which is starting from the smallest element first. We know that the smallest element need to be on either leftmost end or rightmost end. We can just pick the one end that is closest to the smallest element's position (Whether moving it to the left or right end doesn't affect the remaining sequence. So it's better to move it to the closest end). That's it! we do the same thing to the other elements.

After finishing the first two problems, I only had 1 hour left. So I changed my strategy to just solving the small inputs on the last two problems. I will give brief ideas of how to solve the small inputs on the last two problems. The ideas are quite straight-forward to me. For C: Don't Break The Nile , we want to find the maximum flow of water from the south side to the north side of the river. Each grid cell can carry only 1 unit of water to its adjacent cells. Thus, let's build a network flow graph.

  1. For each cell, we create 2 nodes called lower node and higher node, and have an edge with capacity = 1 going from the lower to the higher node. If the cell contains a building, set the edge's capacity to 0.
  2. Now create edges between the adjacent cells: connect the higher node of a cell to the lower node of each adjacent cell with an edge of capacity 1.
  3. Make a new node called source and connect it to all the lower nodes of the south side cells.
  4. Make a new node called sink and connect it to all the higher nodes of the north side cells.
That's all. Then the answer to the problem is the maximum flow on this graph. This method, of course, is too slow to pass the large input set that the height of the river can be up to 10e8.

Now, come to the last problem (D: Trie Sharding). Given M strings, we would like to divide it into N smaller groups and make a trie data structure for each group so that the total number of nodes used to make tries is maximized. So we want to find the maximum number of nodes of all possible group arrangements. For the small dataset, M = 8, N = 4, we can just bruteforce all the possible ways to make groups, make tries for each of them. We finally return the smallest number of nodes of all the possible ways of dividing up the strings into groups. This is all about implementation. For the large dataset M = 1000, N = 100, I still have no idea how to solve it.

This post is a bit long, but that's it for GCJ2014 Round 2. I really like the problems which I can categorize them to be in a hard level for me, but improving requires solving hard problems :). I'm planning to join Round 3 as well, but before that we should read the analysis of this round and try coding it up!

Problems: Code Jame 2014 | Round 2 | Problems
Scoreboard: Code Jame 2014 | Round 2 | Scoreboard


Now that, I'm starting to write blogs seriously in order to summarize what I learn from programming competition. I think it's better to rethink about problems and how well I do during each contest. This can help me prevent repetition of the same mistake. Writing can make ideas solid so that when I see the similar problem again, I can pick up the idea pretty fast. Furthermore, it might be able to guide others about the ideas to solve problems. Thanks to this blog which motivated me to start writing blogs again.