Showing posts with label explanation. Show all posts
Showing posts with label explanation. Show all posts

Monday, April 27, 2015

SRM 657

I guess I just solved the div1 easy. Tried the div1 medium, modulos, modulos , modulos. But I finished the div1 easy editorial, you can find it at http://apps.topcoder.com/wiki/display/tc/SRM+657. Or here, because really, why not:

The problem consists of 6 variables: E, EM, M, MH, H, they are the number of problems you can use in problem sets. Each problem set needs an Easy, A Medium and a Hard problem to be used. E,M and H problems MUST be used as easy, medium or hard, respectively. There are EM problems that can be either easy or medium and MH problems that can be either Medium or Hard. What is the maximum number of problem sets you can make without repeating any problem? Oh, and each variable is a integer as long as `10^18`.

Two decisions are involved in this problem:

  • Of the EM problems that can be easy or medium, how many will be assigned as medium? Let that number be `a`
  • Of the MH problems that can be medium or hard, how many will be assigned as medium? Let that number be `b`

When `a` and `b` are known, we can calculate the number of problems of each difficulty we have available:

  • `E + ("EM" - a) ` easy problems.
  • `M + a + b` medium problems.
  • `H + ("MH" - b) ` hard problems.

To have a problem set we need one of each kind of problems, so the maximum problem sets is `min(E + "EM" - a, M + a + b, H + "MH" - b)`.

Imagine we wanted to see if it is possible to have `x` problems in total:

  • If `E + "EM" < x`, no matter what value of `a` we pick, the number of easy problems would be too small.
  • If `E + "EM" >= x`, then there are normally enough easy problems unless `a` becomes too large. We can find the maximum allowed value of `a`: `M_a = x - E + "EM"`. Also `a` cannot be larger than `"EM"`
  • Similarly, `H + "MH"` must be at least `x` and `M_b = x - H - "MH"` is the maximum allowed value for `b`.
  • We just need `M + a + b` to be at least `x`, imagine we take the maximum allowed values: if `M + M_a + M_b >= x` then we can know `x` is a valid number of problem sets

Now note that if a value of `x` is allowed, then so will all smaller values and if a value of `x` is not allowed , no value larger than `x` will be allowed. So we can use Binary Search to find the maximum allowed value for `x`, this is the maximum possible number of problem sets.

    
long allowedX(long E, long EM, long M, long MH, long H, long x)
{
    if (E + EM < x) {
        return false;
    }
    long aa = std::min(EM, E + EM - x); //maximum a
    if (H + MH < x) {
        return false;
    }
    long bb = std::min(MH, H + MH - x); //maximum b
    return (M + aa + bb >= x); 

}

long maxSets(long E, long EM, long M, long MH, long H)
{
    // We have to maximize:
    // min(E + EM - a, M + a + b, H + MH - b)
    // where 0 <= a <= EM , 0 <= b <= MH
    long lo = 0; // we know 0 is allowed
    long hi = M + MH + 1; // we know this will never be allowed
    while (lo + 1 < hi) {
        long x = (lo + hi) / 2;
        if (allowedX(E,EM,M,MH,H, x)) {
            lo = x;
        } else {
            hi = x;
        }
    }
    return lo;
}

That was the solution in the editorial, but I prefer my lambda solution:

long maxSets(long E, long EM, long M, long MH, long H)
{
    // We have to maximize:
    // min(E + EM - a, M + a + b, H + MH - b)
    // where 0 <= a <= EM , 0 <= b <= MH
    return BinarySearch<long>( 0LL, H + MH + 1,  [&](long x) {
            if (E + EM < x) {
                return false;
            }
            long aa = std::min(EM, E + EM - x); //maximum a
            if (H + MH < x) {
                return false;
            }
            long bb = std::min(MH, H + MH - x); //maximum b
            return (M + aa + bb >= x); 
    });
}

It uses this Binary Search library function because it is funny to use functions as arguments.

// Library code
// takes lo, hi, P() such that:
// P(lo) is true
// P(hi) is false
// If P(x) is true and (x > y) then P(y) is true
// If P(x) is false and (x < y) then P(y) is false
// returns the maximum x such that P(x) is true
template<class D> D BinarySearch(D lo, D hi, std::function<bool(D)> P) {
    while (lo + 1 < hi) {
        D ha = lo + (hi - lo) / 2;
        if (P(ha)) {
            lo = ha;
        } else {
            hi = ha;
        }
    }
    return lo;
}

Saturday, August 30, 2014

SRM 631 : A new arena

Remember when I wrote blog posts periodically? Remember when I actually had energy to have an explanation ready just by the end of the match? Remember when editorials weren't super late? It was a nice time for sure.

Participated in Topcoder SRM 631 . It was a special match because earlier this week the Web version of the Topcoder arena was released (in beta). There is actually a thing in which if you use the arena in SRM 631 or 632 and fill a survey you can maybe, if you are super lucky, maybe, win a ticket to the TCO. So I decided to use it in these matches. The new Arena currently lacks many features. And I have the impression that there were some few wasted opportunities in regards to how it was designed. I will focus this post on what I experienced about the new arena. If

Registration

I was very busy in the morning finishing the first part of SRM 630 editorial (log in required, which is terrible). I've had a very bad streak with my editorial work. First I was actually solving the problem in SRM 629 quite fast, even figured out div1 medium by myself and all, but the div2 hard got me stuck for too long, so long that SRM 630 started. I made the foolish decision to finish 630 before taking back to work in 629., thinking that at least then only one editorial will be late. That was a very bad decision because it turns out that div2 hard and div1 medium were both the same problem. One with a very easy solution that I couldn't understand for over a week, not helped by a paper that made me feel dizzy when reading it...

The short story is , I was busy and distracted so when it came time to register for the match , I forgot to use the web applet (I was already using the Java one, because I needed to test the SRM 630 problems and the web applet doesn't have practice or plugins). Lucky because it turns out that people who tried to register using the web Arena had issues.

One of the Use Cases I had in mind regarding the web arena was the ability to register to the match from a mobile device. You cannot run the Java Arena in Android or iOS. Unfortunately, my attempts to load the arena in Android Firefox were futile and I didn't have time to unfreeze the Google Chrome Android app so I could test it.

I really hope that whatever issue is making it so hard for the web app to load in mobile browers (or maybe just Mobile Firefox) is eventually not a problem. Will make sure to report once I have more info.

The match

I opened the web arena and looked for a way to enter my room. In the competition room, you meet this:

It actually took me a while to find the problem links. They are in the right. The web arena actually shows you the problem names before you open the problem. This has interesting consequences. Remember that the submission score starts to drop the instant you open a problem, so in topcoder matches we tend to try to solve one problem at a time and usually it is best to start with the easiest (lowest score) problem first. Some problem names are very descriptive , however, so maybe if a problem hints you up that the solution will be too easy or too hard for you the problem names are useful information. Or maybe disinformation if the problem name is not that good. Also, in some cases the problem name can give you a hint of who is the writer of the problem...

I don't really like that you need to click "Unopened" to open the problem. It is just not intuitive. I would have preffered an [open] link/button and some test below problem name to indicate if you already opened. Optimal would be to show the problem score, decreasing real time.

I open the div1 easy problem, remember that I am in low energy mode. It took me a while to read the statement. In this problem, you have to turn a board containing either Black or White cells into a board such that, for each column, the maximum number of consecutive cells (in the column) of the same color is `N/2`. A step consists of picking a row and either painting all white cells black or all black cells white. Note that this is the same as painting the whole row white or black. Return the minimum number of moves. I was trying to solve this problem when I noticed something was missing : The constraints. Turns out that the problem statement didn't display them... This is the kind of bug that we could have caught before the match if there was a practice feature - Just saying.

So I needed to open the problem in the Java arena to read the constraints (If the maximum `N` is 8 the problem is straightforward, if the maximum `N` is 1000 it would be very difficult to make it run in time, Constraints are everything). Logging in the Java arena forces the web arena to close. Logging in the Web arena causes the Java arena to freeze. Both arenas need a constant connection, and I was hoping the web one didn't (maybe make it so having a constant connection enables chat and real time score update but is optional).

On the bright side, having to open the problem in the Java arena meant that the Greed plugin would generate test code for me.

How to solve div1 250

The constraints were: Matrix is size `N times N`, `N ` is even, `N <= 50`.

I think the key here is to understand how the columns work. There can only be at most one group of more than `N/2` consecutive cells of the same color in a single column. If no such group exists, we can ignore the column. So we end up with a problem in which each column has a group of at least `N/2 + 1` that we need to cut. The cool property here is that at least 2 rows will be shared by ALL of these groups. Even in an extreme case, if there were 2 groups in two columns, one group was topmost and the other bottommost and the number of consecutive equal colors was `N/2+1` in both columns; even in this case, the two groups would share two rows. It is nice that there will always be two shared rows, because we can always just paint one of them white and the other black and it will be enough to "cut" all the columns. In short: We can always solve the problem in 2 steps. So we only need to worry about two special cases : a) If the board is already correct then the result is 0 steps. b) Maybe the board can be solved in a single step, pick one of `O(n)` rows and paint either completely black or completely white and check if the board is correct. This leads to an `O(n^3)` algorithm.

class TaroJiroGrid:
 def getNumber(self, grid):
    n = len(grid)
    # result is at most 2
    res = 2
    
    def goodGrid(g):
        def goodColumn(i):
            last = '!'
            x = 1
            for j in xrange(1, n):
                x = x + 1 if g[j][i] == g[j-1][i] else 1
                if x > n / 2:
                    return False
            return True
        
        return all(goodColumn(i) for i in xrange(n))
    
    if goodGrid(grid):
        return 0
    
    for i in range(n + 1):
        for color in ('W', 'B'):
            ngrid = [ grid[j] if i != j else color * n for j in xrange(n) ]
            if goodGrid(ngrid):
                return 1
            
    return 2

So I still had to open the web arena, paste the code and submit... But first I wanted to test in the arena. I noticed something cool, unfortunately the screenshot wasn't saved for some reason. Each example had a checkbox next to it. Then there was a [Test All] button. So you can test multiple examples at once. Thus killing one of the main reasons most people use plugins... Unfortunately, when I tried to do this I had an error that EXACTLY ONE example must test. Apparently this is a bug.

The rest

I opened div1 500. Seemed difficult. Then I went to eat. Came back and Tried to chat in the arena, but scrolling text in the chat box turned out to be very difficult. I hope this gets fixed.

I wish the new arena didn't forcefully require a constant connection. It is very annoying to log back in when your internet has a small hiccup... Also hope that whatever makes me unable to use it in mobile gets fixed (either the browser or the arena, will research more later).

Random screenshot with some glitches:

Some of the glitches are my fault : I force a minimum font in the web browser. Some seem to have been caused by slow internet or something.

Thursday, July 10, 2014

SRM 627: Did I really code THAT?

Is this just fantasy? I am not sure if this actually happened, backtracking with binary indexed trees? What?

Div1 250: The one with letters

Game starts with a multiset of alphabetic letters. In every step, two distinct letters are taken and removed. Eventually, the multiset will be empty or contain only repeated letters, those letters are called winning letters. Return all the letters that can possibly be winning letters.

My idea, which I am not totally sure is correct. Is to check for each letter `c`, if it is possible that it will win. Then all the moves have to have the intention that the specified letter remains at the end. This means that we should first try to clear as many letters different to `c` as possible. Eventually, a list of equal letters that are distinct to `c` will remain, and we need to get rid of them by using the `c` letters. If these repeated letters are less than `c`, then `c` can win.

The trick then is to always pick pairs of letters distinct to `c` , and pick the two letters that are currently most frequent.

class HappyLetterDiv1:
 def getHappyLetters(self, letters):
    res = ''
    # for each letter ch, can we do it?
    for ch in sorted(set(letters)):
        # get a dict of distinct letters and counts
        other = { x : letters.count(x) for x in letters if x != ch }
        while len(other) > 1:  # at least two different ones
            # sort by frequency
            least = sorted([ (other[x],x) for x in other.keys() ])
            # remove a letter of each of the two most frequent:
            a = least[-1][1]
            b = least[-2][1]
            def dec(y):
                other[y] -= 1
                if other[y] == 0:
                    other.pop(y)
            dec(a)
            dec(b)
        if (len(other) == 0) or (other[other.keys()[0]] < letters.count(ch)) :
            res += ch
    return res

Div1 500: The one with a graph and inversions

You have a graph of at most `1000` vertices. The graph is special in which it is a tree with an extra edge added onto it. (The graph is connected, undirected, lacks loops and has exactly `|V|` edges)

The value of a vertex `x` is `V[x]`.

For a simple path of vertices in the graph, we make a sequence `S` of the values of the vertices in visited order. Now count the number of "inversions" of the graph. This is the number of pairs `(i,j)` such that `(i < j)` and `(S[i] > S[j])`. Return the minimum number of inversions in a sequence of at least `K` elements.

.... So the trick is that the graph is a tree with exactly one extra edge. So there is only one simple cycle in it. So there are at most 2 simple paths connecting each pair of vertices. So there are at most 2000000 paths. 2000000 sequences. With backtracking, we can visit them all. The problem is that you need to calculate the sequence's number of inversions as you build the sequence...

Let's say you have already chosen some of the previous elements in a sequence, and want to add `V[x]` to the sequence, we can count the number of added inversions by calculating the number of elements in the sequence that are higher than `V[x]`. So we "just" need to use a data structure that adds elements, removes elements and counts the number of elements larger than a integer `x` in `O(log(N))`. And we need to update that sequence (add , then remove) as part of the backtracking. Easy... So I implemented a binary indexed tree , called it fenwik for some reason and it seems to work.

// for each pair of edges (x,y), there are at most 2 paths.
// thus there will be at most 2000000 paths.


int fenwick[2048]; //probably wrong name

void fenwick_add(int x, int a = 0, int b = 1024, int node = 0)
{
    if ( a <= x && x < b ) {
        fenwick[node]++;
        if (a < b - 1) {
            int c = (a + b) / 2;
            fenwick_add(x, a,c, node*2 + 1);
            fenwick_add(x, c,b, node*2 + 2);
        }
    }
}
void fenwick_remove(int x, int a = 0, int b = 1024, int node = 0)
{
    if ( a <= x && x < b ) {
        fenwick[node]--;
        if (a < b - 1) {
            int c = (a + b) / 2;
            fenwick_remove(x, a,c, node*2 + 1);
            fenwick_remove(x, c,b, node*2 + 2);
        }
    }
}
int fenwick_counthigher(int x, int a = 0, int b = 1024, int node = 0)
{
    if (a > x) {
        return fenwick[node];
    }
    if ( (a==x) || (b <= x) ) {
        return 0;
    }
    int c = (a + b) / 2;
    int r = fenwick_counthigher(x, a,c, node*2 + 1)
          + fenwick_counthigher(x, c,b, node*2 + 2);
    return r;
}


int getMinimumInversions(vector<int> A, vector<int> B, vector<int> V, int K)
{
    int N = V.size();
    vector<list<int>> g(N);
    for (int i = 0; i < A.size(); i++) {
        g[A[i]].push_back(B[i]);
        g[B[i]].push_back(A[i]);
    }

    const int INF = 20000000;
    int res = INF;
    memset(fenwick, 0, sizeof(fenwick));
    for (int s = 0; s < N; s++) {
        vector<bool> visited(N, false);
        int t = 0;
        int inv = 0;
        
        std::function< void(int) > backtrack = [&](int x) {
            visited[x] = true;
            t++;
            int oinv = inv;
            inv += fenwick_counthigher( V[x] );                
            fenwick_add( V[x] );
            if (t >= K) {
                res = std::min(res, inv);
            }
            for (int y : g[x]) {
                if (! visited[y] ) {
                    backtrack(y);
                }
            }
            fenwick_remove( V[x] );
            inv = oinv;
            t--;
            visited[x] = false;
        };
        backtrack(s);
    }
    
    
    
    return (res >= INF) ? -1: res;
}

Challenge phase

Got lucky, the first place in the room clearly did 500 without the data structure part (Was using a std::multiset, but multiset doesn't help in the counting higher elements part). So I just challenged with a large case.

Wednesday, June 04, 2014

SRM 623: Saved by the challenge phase

Div1 300: The evil one

This problem was evil. Let me explain, you have a board of maximum size 20x20, some cells contain apples, some pears, some are empty. A valid move is to move a fruit to an empty cell. You can do at most `K` moves. The objective is to maximize the size of a rectangle containing only apples. What is the maximum?

Say you already picked an arbitrary rectangle, there are `O(n^4)` of those. There are two things that are important about this rectangle: `a` : Number of apples in side, `p` Number of pears inside, `e` number of empty cells inside. You can also know the number of apples/pears/empty cells outside the square. Finding this values is a complicated implementation part. Also, I think too slow for python, so I tried to do something in c++, using lambdas of course. It took eons. But when I finally did it , I had a new problem...

Knowing that information about the rectangle we need to find the minimum number of moves needed to have only apples in the rectangle.

At first I was trying something very wrong: Move each pear to a (different) empty cell outside, then move all the apples inside. This took me ages to debug, and when I finally passed system tests with it, I was about to click submit when I started to wonder "Is this really it?" , and I noticed that noooo, the minimum number of moves is much trickier to find. Hence the examples were weak.

So what is the real optimal strategy? Well, the first issue is that we only need one empty cell in the board. We can keep reusing it to do all the moves. Then we have this: If there are any empty cells in the square, it is easy to see that it is optimal to just move any outside apples and cover them. The remaining case is when there are some pears inside the rectangle, at least one empty cell outside and enough apples outside. A single pear is treated like this: Move pear to empty cell, move apple to the position the pear was in, this leaves an empty cell in the apple's position, and we can repeat.

So in fact, the minimum number of moves is equal to:

  • 0 if all the cells already contain apples.
  • # of empty cells inside the rectangle, if the rectangle contains no pears.
  • # of empty cells + 2 * number of pears inside the rectangle, if and only if there is at least one empty cell in the board
int getBoard(vector<string> board, int K)
{
    // stuff to count the number of apples / pears / empty inside a rectangle:
    int W = board.size(), H = board[0].size(); 
    map<char, vector<vector<int>>> total;
    string chars = ".AP";
    for ( char ch: chars ) {
        vector<vector<int>> t(W+1, vector<int>(H+1,0));
        for (int i = 1; i <= W; i++) {
            for (int j = 1; j <= H; j++) {
                int x = ( board[i-1][j-1] == ch );
                t[i][j] = x + t[i-1][j] + t[i][j-1] - t[i-1][j-1];
            }
        }
        total[ch] = t;
    }
    auto rectangle = [&](char ch, int x0, int x1, int y0, int y1) {
        auto &t = total[ch]; 
        return t[x1][y1] - t[x0][y1] - t[x1][y0] + t[x0][y0];
    };
    
    int A = rectangle('A', 0, W, 0, H);
    int E = rectangle('.', 0, W, 0, H);
    
    int res = 0;
    for (int x0 = 0; x0 < W; x0++) {
        for (int y0 = 0; y0 < H; y0++) {
            for (int x1 = x0 + 1; x1 <= W; x1++) {
                for (int y1 = y0 + 1; y1 <= H; y1++) {
                    // the actual logic:
                    // inside:
                    int e = rectangle('.',x0,x1,y0,y1);
                    int a = rectangle('A',x0,x1,y0,y1);
                    int p = rectangle('P',x0,x1,y0,y1);
                    
                    // out side:
                    int na = A - a;
                    int ne = E - e;
                    
                    if (e <= na) {
                        int x = e;
                        int mov = x;
                        na -= x;
                        a += x;
                        e = 0;
                        ne += x;
                        if (mov <= K) {
                            if (p == 0) {
                                res = std::max(res, a);
                            } else if ( (p <= na) && (ne >= 1) && (mov + 2*p <= K) ) {
                                res = std::max(res, a + p);
                            }
                        }
                    }
                }
            }
        }
    }
    return res;
}

Div1 450: The difficult one

All I can think of is that we can imagine a different problem, the points stay static and the player moves either in a vertical line upwards (increasing `y` coordinate) or in a diagonal (incrementing `y` and incrementing or decrementing `x`).

Challenge phase

I took too long to solve div1 300, but I did learn that the example cases were weak. So I thought of a challenge case to make my initial solution fail (using only one empty square). Luckily my room wasn't very aggressive and I could find 2 solutions with that wrong idea and earned 100 points.

Thursday, April 24, 2014

SRM 618: When python failed

This is the eighth of ten matches in which I promised to use python exclusively. This is also the first match in which I feel like I can claim I lost plenty of points because of this restriction.

Div1 250: The one with heteronormativity

So I am supposed to be solving this problem, and all I can think of after reading for the first time is that the problem's definition of family allows incest but is all about not allowing same-gender pairings :/ okay. ..

So a "family" is defined as a graph, in which each edge either has zero incident edges or exactly two incident edges. The two incident edges MUST be from two nodes with different genders. Also the graph must be topsorted. Given a graph of up of 100 nodes return if it could be a family or not. Basically, you have to assign genders to each node. It turns out that the input format guarantees that the graph is topsorted and that each node has 0 or 2 incident nodes. So all that we need to do is ask if we can assign the genders correctly.

If two nodes appear as the parents of a node, then their genders must be different. If we imagine a different graph, in which each of these pairs of nodes that must have a different gender are connected by a single edge. Then the graph must be bipartite. If the graph is bipartite, then the answer is that Yep, the graph could be a "family". Else not. Checking if a graph is bipartite is an easy application of the Depth-first search.

class Family:
 def isFamily(self, parent1, parent2):
    n = max(max(parent1),max(parent2)) + 1
    # Make a set of edges in the hopefully bipartite graph, they are undirected 
    E = set(zip(parent1, parent2)) | set( zip(parent2, parent1) )
    E.remove( (-1,-1) )
    
    # is the graph bipartite?
    color = [-1] * n
    result = [ "Possible" ]
    def dfs(x, c):
        if color[x] == -1:
            color[x] = c
            for (u,v) in E:
                if u == x:
                    dfs(v, 0 if c == 1 else 1)
        elif color[x] != c:
            result[0]  = "Impossible"
        
    for i in range(n):
        if color[i] == -1:
            dfs(i, 0)
        
    return result[0]

I took a bit of time mostly because of the above-mentioned distraction :P, but python helped me keep the code clean. I think it should pass unless 100x100 is suddenly too slow (I doubt it).

Div1 500: The one with strings.

You should count the number of maximal-length strings that use an alphabet of `n` letters and follow these properties:

  • No consecutive equal letters.
  • For each pair of (possibly equal) characters X and Y, string "XYXY" is NOT a subsequence of the string.

The first issue is how to know what is the maximum length possible. After plenty of analysis, I found that the maximum length possible is 2n-1. However, there are two methods to achieve it:

  • One way, is to surround a string of 2(n-1)-1 length using (n-1) alphabet characters, using two instances of one character different to those (n-1) ones. For example: A????????????A, where ????? contains no A.
  • The other way has 3 instances of the extreme character. A??????A!!!!!!!A, but also ?????? and !!!!!! cannot share characters.

So the rest is to count the number of valid patterns. It is not necessary to worry about which character goes to which position of the pattern, because we can just multiply the result by `n!`.

The rest is an `O(n^2)` dynamic programming, unfortunately, `n` is up to 5000 (I wish it was 1000), so no matter what I do, the thing will take at least 6 seconds. That's too much.

More info about the recurrence in the comments of the code:

MOD = 1000000007

def modFactorial(x):
    r = 1
    for i in xrange(1, x + 1):
        r = (r * i) % MOD
    return r
        

class LongWordsDiv1:
 def count(self, n):
    # The maximum number of letters is 2*n-1, although there are multiple ways 
    # to reach that maximum
    # Example, for n = 5
    # 012343210
    # 010234320
    # 010232420
    # 012103430
    # 012321040
    # 012131040
    # there should be 9, but can't think of the other 3
    
    dp = [0] * 5001
    dp[0] = 1
    dp[1] = 1
    for i in xrange(2,n+1):
        #1) 2n - 1 = 2 + (2(n-1) - 1)
        res = dp[i-1]
        #2) find a,b: 2n - 1 = 3 + 2a + 2b - 2
        #                 b  = (2n - 1 - 3 - 2a + 2) / 2
        #                 b  = (2n - 2a - 2) / 2
        #                 b  = n - a - 1
        a = 1
        while a < i:
            b = i - a - 1
            if b > 0:
                res += (dp[b] * dp[a]) % MOD
            else:
                break
            a += 1
        dp[i] = res

    return ( dp[n] * modFactorial(n) ) % MOD

It is possible I missed something in this analysis, though.

Monday, April 21, 2014

Topcoder SRM 617 recap: regular

This SRM took me by surprise, I think the programming contest calendar I use wasn't updated correctly or I didn't read it correctly the first time I read it, so I thought SRM 617 was another date. On Sunday, I got the Topcoder email and surprise!. It's a good thing that we no longer have that bug in which invitation emails don't get sent...

This is the 7-th of 10 SRMs in which I am using python exclusively. So far I don't think it has caused any negative impact to my rating. I think that unless you are in such a high rating position that you really need frequent div1 1000s, python's drawbacks are not a big deal and the pros tend to compensate for the cons :/

Div1 250: The one with cutting cake

You have the nonsensical notion of a one-dimensional cake of length `n`. Your friends are going to come visit, all you know is that the number of friends will be a proper divisor of `n`, but you are not sure which one. You want to make cuts to the cake in such a way that you can distribute the contiguous pieces evenly to your friends, no matter what the number of friends is. The pieces a friend receives may be of different length and the number of pieces each friend gets is not necessarily always the same, the only condition is that they are contiguous after cutting the cake and that the total length of cake each friend receives is equal. Return the minimum number of cuts needed so that this always works, regardless of the number of friends.

Well, the key is that the amount of cake should be the same for each friend, so for each `y` that is a proper divisor of `n` and `x = n/y`, there MUST exist cuts at `x,2x, 3x, ... `, The problem asks for the minimum, so we shouldn't do any more cuts than those `x,2x, 3x, ... `, for each `x`. The trick is that some values `kx_0` and `rx_1` may be the same for different divisors `y_0, y_1` . So we should just count all the numbers that are equal to some `k * (n//y)` where `k` is a integer, `y` is a divisor of `n` and `k * (n//y) < n`.

So simple simulation will be fine. During the match, I had the feeling that with `n <= 100000` and the 2 seconds limit, this simulation would be a bit slow for python. I did things like getting the divisiors in `O(sqrt(n))` time. And even after that, I made sure to test the worst input. The worst input is the number `n <= 100000` that has the most divisors. This is equal to the largest possible highly composite number or 83160 , in short. It turns out it wasn't necessary, also, with some knowledge of iterators and sets, the code can be much simpler than the code I submitted during the match:

class MyLongCake:
 def cut(self, n):
    cuts = set()
    for d in properDivisors(n):              # for each proper divisor d:
        cuts |= set( xrange(n/d, n, n/d) )   # add n/d, 2n/d, 3n/d, etc to set
    return len(cuts) + 1                     # return number of cuts + 1

def properDivisors(n):
    return (i for i in xrange(1, n) if n % i == 0)

During the challenge phase, you'd figure from reading other people's codes that the integers that get cut are exactly those who are not coprime with `n`. So result is actually `n - phi(n)`

Div1 800: strings

I opened this problem accidentally when I intended to open div1 500 :/ . It seemed like I had no idea how to solve it. Also, when a problem score is much smaller than usual, it is possibly much harder than usual (Sorry admins, everyone knows you are backward when choosing problem scores). so I skipped to div1 500, a bit too late, perhaps.

Div1 500: The one with pie and dolphins

You have 50 programmers, for each `i < n`, where `n <= 1000`, you should choose between:

  • Giving a dolphin to programmer `C_0[i]` and a pie to `C_1[i]`.
  • Or
  • Giving a dolphin to `C_1[i]` and a pie to `C_0[i]`.

After you give all these pies and dolphins, each programmer will calculate the absolute value of the difference between the number of pie and dolphin they got. You want to minimize the total sum of all these absolute values.

ooh boy. So I stared at the computer screen for more than half an hour. Everything I thought was non-sense for a good while. Eventually, I decided that the problem was most likely greedy. So the new issue was how to think of a greedy solution. If you process the decisions in the order of i, it is difficult to predict what will happen later and how it should affect your decision. But there is no need to do that. You can really do a decision whatever time you want. A decision will change the result in -2,-1,0,1 or 2, points. What we can try, is repeat this 1000 times: Pick the decision (of all not picked yet) that will change the score for the best. This is easily implemented through just simulation. Complexity is `O(T^2)` where `T` is the number of choices.

I was typing this, and had some bugs. It took longer than usual to debug because it is not easy to know if an answer is correct or not. (This was the first Topcoder problem ever that allowed multiple correct answers to the same test case). Eventually, I found out that in two lines I had i where I meant j. Too bad, coding phase already ended. I tried the solution in the practice room after fixing it , and it passes although the execution time gets awfully close to 2 seconds .

class PieOrDolphin:
 def Distribute(self, choice1, choice2):
    choices = zip(choice1, choice2)
    n = 50  # values of choice1, choice2 go from 0 to 49, we can assume 50 people
    pie = [0] * n
    dolphin = [0] * n
    res = [ 3 ] * len(choices)
    for i in range(0, len(choices)):
        best = (3, (0,0), -1, 3)
        for (j, (a,b) ) in enumerate(choices):
            if res[j] == 3:
                # try (a,b) or (b,a) for choice:
                for (x,y,z) in ( (a,b,1), (b,a,2) ):
                    # calculate how the score would improve
                    p = abs(pie[x] - dolphin[x] + 1) -  abs(pie[x] - dolphin[x])
                    q = abs(pie[y] - dolphin[y] - 1) -  abs(pie[y] - dolphin[y])
                    best = min(best, (p+q, (x,y), j, z) )

        (x , (a,b), bi, t) = best
      # print (a,b),' would improve by ', x, ' index ',bi
        res[bi] = t
        pie[a] += 1
        dolphin[b] += 1
    return tuple(res)

So why is this correct? Well, no idea, but I thought it should be correct because , in theore, each decision is used in the best way ever. And each decision should reduce the effectiveness of at most one decision, in theory. I have nothing formal to prove.

I know that the real answer involves turning the thing into a graph and doing some proofs to show that it is easy. I think that this graph solution eventually reduces to this greedy. But no idea yet.

So?

I think it was a fine problem set, if a bit unusual (odd problem scores, flexible answers in div1 500).

Tuesday, January 21, 2014

SRM 605

A slow day. It was an 8:00 AM match and I may or may not have stayed up till 3:00 AM the day after writing a very bad editorial. So I wasn't at 100% speed.

250: The one with hamburgers

You are given taste[] and type[], each hamburger is of type type[i] and has a taste value taste[i]. The joy to eat is equal to the total sum of the hamburgers chosen multiplied by the number of distinct types eaten. What is the maximum joy? (Note multiple hamburgers may be of the same type and that taste[i] can be negative). We can choose to eat zero hamburgers, so result should never be negative.

Let's say you fix the number of types `y`, then we should find the `y` types with the maximum sums of tastes and sum those tastes together. Picking the best taste sum of each type is a separate problem of its own. Normally, you should pick all non-negative tastes . The only reason to pick a negative taste is when there are only negative tastes of a given type. In that case, you should only pick one negative number, the largest one (closest to 0). Sometimes, even though a type has negative sum, it is a good idea to try it because it increases the number of types.

The rest is to solve the implementation of finding the best possible sum of tastes for each type and then the best sum of `y` types. I used the STL which makes everything cute:

int getNumber(vector<int> type, vector<int> taste)
{
    // first of all, save all tastes for each type.
    map<int, vector<int> > typeTastes;
    for (int i = 0; i < type.size(); i++) {
        typeTastes[ type[i] ].push_back(taste[i]);
    }
    vector<int> tasteResult;
    // for each type:
    for (auto &q: typeTastes) {
        auto &p = q.second;
        // sort tastes in non-increasing order:
        sort(p.rbegin(), p.rend());
        // Find the best sum, try to get all the non-negative numbers
        int s = 0;
        int i = 0;
        while ( (i < p.size()) && (p[i] >= 0) ) {
            s += p[i];
            i++;
        }
        // sometimes all numbers are negative... 
        if (i == 0) { 
            s += p[0]; //pick the largest
        }
        //add this sum to the list of types:
        tasteResult.push_back(s);
    }
    // now try the best number of types:
    sort(tasteResult.rbegin(), tasteResult.rend());
    int res = 0, sum = 0;
    for (int i = 0; i < tasteResult.size(); i++) {
        sum += tasteResult[i];
        res = std::max(res,  (i + 1) * sum );
    }
    return res;
}

450: The one with sets

You have a set of all natural numbers from 1 to `2N`, inclusive. Partition the set in two sets of the same size such that, after sorting the sets, the absolute value of the difference of their i-th elements is at least `K`. Given `N <= 50` and `K <= 10`, count the total number of valid ways to pick these partitions.

As usual, I wasted most of my time coding a dynamic programming that was actually wrong. I didn't at first notice that the sets should be sorted ...

Anyway, the trick is that `K <= 10`. Assume you decide the contents of the sets going from highest integer to the lowest. You begin deciding the position of `n = 2N`, then `n = 2N-1` and so and so. You can represent the integers added to each set using two things: a) The number of integers whose difference with the current number is at least one in each of the two sets and b) The bitmasks representing the set of the other integers that went to each of the two sets. After some inspection, you only need one bitmask and one count, the other set's can be found using `N` and `n`.

Anyway, let's say you want to add `n` to set A. From the bit masks and counts there are four things we should know:

  • `bA` : The number of bad numbers in A, numbers smaller than `n + K`.
  • `bB` : The number of bad numbers in B, numbers smaller than `n + K`.
  • `gA` : The number of good numbers in A, numbers that are at least `n + K`.
  • `gB` : take a guess

Since `A` and `B` are sorted, you can assume that the last `bA + gA` spots in `A` are taken and the last `bB + gB` spots in `B` are taken.

If we want to add `n` to `A` one of the following must be true:

  • The largest position in `A` will be matched to a good number in B
  • The largest position in `A` will be matched to nothing in `B`.

I am pretty sure this logic is correct, but when I learned it I had only 8 minutes to implement it and it is quite complicated to.

Saturday, January 11, 2014

SRM 604: More slow

Hmnn, another SRM in which speed in 250 can break or make it for a yellow coder. And I am too slow lately... I couldn't solve 550

Div1 250: The one with powers of 3

You start at `(0,0)`, in each step `(k >= 0)`, you can add/subtract `3^k` from exactly 1 of the coordinates. Is it possible to reach `(x,y)` in any number of steps? (possibly 0?)

First of all, `(0,0)` can be reached in 0 steps, disregard this special case. Otherwise, we assume that we make at least 1 step. How about we undo the first step? The one in which we only moved by 1 unit? Try the 4 possible directions for this first step and undo them. This modifies the problem to: Can we reach `(x,y)` using steps of length `3^k`, but this time `(k > 0)` ? Now since all steps changed a coordinate by a power of 3 different to 1, then both `x` and `y` must be multiples of 3. If that isn't the case, this is invalid. Else since both `x` and `y` are multiples of 3, we can actually just divide them to by 3. This converts all moves `3^k` to `3^(k-1)` which means that the problem is now exactly as it was initially, using moves `3^0, 3^1, ....`, so we can solve it recursively.

We stop recursion whenever we reach `(0,0)` or when multiples of 3 are needed and some of `x` or `y` is not a multiple of 3. This recursion *seems* to work in time.

I tested with what I think gives the maximum number of branching: {-348678440, 116226147} and it works just fine. I think it is unlikely it will time out. But I am unsure. High coders used a completely different approach.

bool able1(int x, int y)
{
    if (x == 0 && y == 0) {
        return true;
    }
    // can we if the first step is 3, second is 9, and so and so?...
    // (all must be a power of 3) greater than 1.
    if ( (abs(x) % 3 != 0) || (abs(y) % 3 != 0) ) {
        return false;
    }
    return able0(x / 3, y / 3);
}
bool able0(int x, int y)
{
    bool good = ( (x==0) && (y==0) );
    for (int i = 0; i < 4; i++) {
        good |= able1(x + dx[i], y+dy[i]);
    }
    return good;
}

string ableToGet(int x, int y)
{
    return able0(x,y) ? "Possible" : "Impossible";
}

Div1 medium, the one with a tree

I initially thought of a dynamic programming solution, and began to code it but took too long. 5 minutes before the end of the match, I noticed some issues with this idea. It was too slow and I wasn't sure how to handle a special case.

I now think of a slightly different dp, but my thoughts are not complete.

So?

A bad day, I just hope the 250 passes system tests.

Monday, January 06, 2014

SRM 603: uncertainty

What a match to be tainted by uncertainty. I have no idea if my 250 is correct and for a while I had no idea if my 500 was correct. Starting to write before the challenge phase and now I know that my 500 is at least correct in theory. I could have made an implementation mistake.

Div1 250

Two players have a tree. They play alternating turns. In a turn, a player must pick an edge and destroy the edge. This splits the tree into two components (also trees). The player then chooses a component to keep, the rest is discarded. The game ends when only one node remains. The nodes have some costs written on them. The first player wants to maximize this value, the other player wants to minimize it. Return the cost of the node if both players play optimally.

I had absolutely no idea what to do. I tried everything and couldn't think of a non-clever way to solve it. Eventually, I thought that since it is a div1 250, it shouldn't be so hard. It probably has a very easy solution that I am missing. Maybe a super easy solution that is, however, difficult to prove... I checked the example cases and noticed that they were all very simple... maek you think... Then I noticed that in all those cases, the returned value was the maximum cost node with degree equal to 1. After drawing in paper it sort of makes sense. I was late so I made a submission. Right now it seems that just about everybody is doing this. So maybe it is correct?

int findend(vector<int> edges, vector<int> costs)
{
    int n = edges.size() + 1;
    vector<int> degree(n, 0);
    for (int i = 0; i < edges.size(); i++) {
        // edge between i+1 and edges[i]
        degree[ edges[i] ]++;
        degree[i+1]++;
    }
    int best = 0;
    for (int i = 0; i < n; i++) {
        if (degree[i] == 1) {
            best = std::max(best, costs[i] );
        }
    }
    return best;
}

Div1 500

Given `n` count the number of pairs of two strings such that the second string is a rotation of the first string and the strings use only the first `k` letters of the English alphabet.

The number of rotations depends on the number of repetitions in the first string. If the string contains 2 repetitions (eg: abab), then the number of rotations is n/2. For each number of repetitions `r`, calculate the number of ways to have exactly that number of repetitions and multiply by `n/r`.

So in order to have `r` repetitions, `n` must be a multiple of `r`. So we are interested in all divisors `d` of `n`. With `n <= 1000000000`, we can find all the divisors in `O(sqrt(n))`.

The difficult part is to count the number of strings with an exact number of repetitions. Note that "aaaa" contains "a" repeated 4 times, "aa" repeated two times and "aaaa" "repeated" 1 time. We should only consider it as 4 repetitions. So if we calculate the number of strings with 2 repetitions as `K ^ (n / r)`, we have to subtract the strings with smaller repetitions that divide `r`... I couldn't think of a way to do this fast. I could only do it in `O(d^2)` where `d` is the number of divisors of `n`. At first I had no idea what an upper bound for the number of divisors is. We know we need an `O(sqrt(n))` algorithm to find them. But what about the actual number of divisors?

In my first excursions, I tried many numbers composed of the first few primes raised to some exponents. My best was 729.

Eventually though, I coded a bruteforce. It appears 1344 is the maximum number of divisors for `n <= 1000000000`. Let's see...

const int MOD = 1000000007;
vector<int> getDivisors(int n)
{
    vector<int> div;
    for (int i = 1; i*i <= n; i++) {
        if (n % i == 0) {
            div.push_back(i);
            if (n / i != i) {
                div.push_back(n / i);
            }
        }
    }
    sort(div.begin(), div.end());
    return div;
}
long modPow(long x, int y)
{
    long r = 1;
    while (y > 0) {
        if (y & 1) {
            r = (r * x) % MOD;
        }
        x = (x * x) % MOD;
        y /= 2;
    }
    return r;
}
long best = 1;
vector<int> primes = {2,3,5,7,11,13,17,19,23};

void backtrack(int p, long x, long divs)
{
    if (divs > best) {
        best = divs;
        cout << x <<" has "<<divs<<endl;
    }
    long y = x;
    int i = 0;
    if (p == primes.size()) {
        return;
    }
    while (y * primes[p] <= 1000000000LL) {
        y *= primes[p];
        i++;
    }
    //cout << "}" << endl;
    for (int j = i; j >= 0; j--) {
        backtrack(p+1, y, divs * (j+1) );
        y /= primes[p];
    }

}

int getNumber(int n, int k)
{
    // The bruteforce to find the maximum number of divisors
    //backtrack(0, 1, 1);
    
    vector<int> d = getDivisors(n);
    long total[d.size()];
    long res = 0;
    cout << d.size() << " divisors found"<<endl;
    for (int i = 0; i < d.size(); i++) {
        long x = modPow(k , d[i]);
        for (int j = 0; j < i; j++) {
            if (d[i] % d[j] == 0) {
                x = (x - total[j] + MOD) % MOD;
            }
        }
        total[i] = x;
        //cout << "for "<<d[i]<<" = "<<total[i]<<endl;
        res += (total[i] * d[i]) % MOD;
    }
    return (int)( res % MOD );
}

So?

The challenge phase is about to end and my solutions are standing. Let's see what happens

I don't really like 250s that have solutions that are easy but difficult to prove. How many people are getting points like me, coding it on a hunch? Well... maybe the solution is wrong,but Petr agrees, so... I gues it is right.

Monday, December 30, 2013

Codeforces "Good bye 2013" round

So it was a special round for coders of both divisions, problems ranged from the super easy problem A to the super difficult problems E,F,G... I always mean to participate in CF rounds but time constraints are tough. Since this was a special match I decided to give it a try.

Problem A: New Year Candles

problem statement

I mentioned this was an easy problem. Just simulate the process. For each candle, use it, increment the used candles counter, if used candles ever reaches `b`, join these used candles together to make a new one: set the counter to 0 and increment the number of available candles. The constraints are small so the simulation is fast enough:

int solve(int a, int b)
{
    int r = 0, u = 0;
    while (a > 0) {
        a --;
        r += 1;
        u ++;
        if (u >= b) {
            a ++;
            u -= b;
        }
    }
    return r;
}

Just because a problem is easy, it doesn't mean it is easy to get a good score in comparison to other coders. Indeed, even though I coded this one as fast as possible, by the time I submitted its code there were already 170+ submissions to this problem and some few submissions to problem B :/

Problem B: New Year Present

problem statement

This is the kind of problem that makes me wish Topcoder allowed problems that didn't need a specific answer, but just following some rules. The number of commands can be anything as long as it is less than 1000000. There are at most 300*300 candles, so even the easiest strategy I could think of was guaranteed to do it in less than 1000000 steps. It works as follows:

  • We fill the wallets from left to right. Begin with the left-most wallet, if needed, put a coin.
  • After putting a coin, robot cannot put a new coin again unless it moves. So we move the robot either right or left (It is best to move it right, since most likely left wallet is already done, but sometimes you cannot move right). If the next wallet is not complete, put a coin before moving back to the original wallet.
  • Once all the coins in the left-most wallet are done, move right, and consider this the new left-most wallet to fill.

Logic is basic and it will use at most 3 steps per coin, so it should work under the constraint.

It was actually tricky to implement. It occured me to make a tester so I could simulate the moves and make sure everything is fine. This was a good idea because I found many bugs.

I wonder how an optimal strategy looks like.

const int MAX = 1000000;
void solve(int n, int * a, string & res)
{
    #ifdef DO_CHECK
        int b[n];
        for (int i = 0; i < n; i++) {
            b[i] = a[i];
        }
    #endif
    res.resize(MAX, '-');
    int x = 0;
    int t = 0;
    while (true) {
        if (a[x] == 0) {
            // wallet x is done, move right
            if (x + 1 == n) {
                break; //everything is done, grats.
            }
            res[t++] = 'R';
            x++;
        } else {
            // put a coin
            res[t++] = 'P';
            a[x]--;
            // move to some other wallet and return back
            if (x + 1 == n) {
                assert(x != 0);
                res[t++] = 'L';
                res[t++] = 'R';
            } else {
                // put a coin if needed
                res[t++] = 'R';
                if ( a[x + 1] != 0 ) {
                    a[x + 1]--;
                    res[t++] = 'P';
                }
                assert(x + 1 != 0);
                res[t++] = 'L';
            }
        }
    }
    assert(t < MAX);
    // The checker runs in case the DO_CHECK #define is on
    #ifdef DO_CHECK
        int c[n];
        fill(c, c+n, 0);
        x = 0;
        for (int i = 0; i < t; i++) {
            //cout << res[i] << endl;
            switch(res[i]) {
            case 'L':
                assert( x > 0 );
                x--;
                break;
            case 'R':
                assert (x < n - 1);
                x++;
                break;
            case 'P':
                assert( (i == 0) || (res[i-1] != 'P') );
                c[x]++;
            case '-':
                break;
            default:
                assert(false);
            }
        }
        for (int i = 0; i < n; i++) {
            assert( b[i] == c[i] );
        }
    #endif

}

Problem C: New Year Ratings Change

problem statement

I am not entirely sure about this one, my solution is to sort the clients in non-decreasing order of `a_i`. It follows that the ratings you assign must be in increasing order (we want them to be different). For each `i` (in the order), try the smallest possible rating (must be at least `a_i` and larger than the rating assigned to the previous client).

I did various tests, I was quite cautious today:). Found a couple of bugs, but fixed them. So I thought it was correct. Well, unfortunately, my first submission I uploaded B.cpp (Solution for the previous problem) by mistake. And in the second submission I failed pretests, I didn't notice the maximum `n` was 3*100000, I initially thought it was 1000000. Changing the array size to 300000 passed pretests, let's see if I survive.

const int MAX = 300000;
// I:
int n;
int a[MAX];
// O:
int b[MAX]; //results
//-----------------------------
int id[MAX];

void solve()
{
    // Since the return must be in the same order as original a_i, we cannot
    // just sort the a_i array. Instead, sort the client indexes.
    for (int i = 0; i < n; i++) {
        id[i] = i;
    }
    sort(id, id + n, [&](int x, int y) -> int {
            return a[x] < a[y]; // I <3 lambdas so much.
    });
    int last = -1;
    // now do it in non-decreasing order of a_i :
    for (int i = 0; i < n; i++) {
        if ( last >= a[id[i]] ) {
            b[id[i]] = last + 1;
        } else {
            b[id[i]] = a[id[i]];
        }
        last = b[ id[i] ];
    }
    
}

Problem D: New Year Letter

problem statement

And so this is when the problems become challenging. The trick is to notice that `k` is sort of small, at most 50... The strings themselves can be quite complicated. For example, is `s_1` is "A" and `s_2` is "C", then `s_3` can have one "AC".

To calculate the final number of AC strings in the final string, we need to know 6 things (Yep, 6):

  • The number `p` of AC in the first string `s_1`
  • The starting character `a` of `s_1`
  • The final character `b` of `s_1`
  • The number `q` of AC in the second string `s_2`
  • The starting character `c` of `s_2`
  • The final character `d` of `s_2`

With this in mind, we can find the number of AC in the final string `s_k` in `O(k)` time. You can do it recursively. There are things you can find about `s_3`:

  • It will start with `a`.
  • It will end with `d`.
  • It will have at least `p+q` ACs, it might have an extra one if `b = "A"' and `c = "C"`.

`s_2` and `s_3` can become `s_1` and `s_2` in a version of the problem that has `k-1` steps.

There are only 3 characters that are relevant to simulate for starting and ending characters: A , C and something else. We can use X for that something else. This means that the number of tuples `(p,a,b,q,c,d)` is `O(nm)`. We can just brute force them until we find a case that returns `x` ACs.

However, not all tuples `(p,a,b)` and `(q,c,d)` are valid. We still need to actually build those strings. This was the most complicated sub-problem in my case. Though probably you can use dynamic programming to make it easier. What I did was a lot of case studying. Thankfully I also tested this throughly. I *think* it is correct

const string WRONG = "Happy new year!";

// given the parameters:
// p: number of ACs in s1
// q: number of ACs in s2
// a: starting character of s1
// b: final character of s1
// c: starting character of s2
// d: final character of s2
// : Count the number of ACs in sk:
int simulate(int k, char a, char b, char c, char d, int p, int q)
{
    if (k == 2) {
        return q;
    }
    int r = 0;
    if (b == 'A' && c == 'C') {
        r = 1;
    }
    long np = p + (long)q + (long)r;
    np = std::min<long>(np, 1000000001LL);
    return simulate(k - 1,  c,d, a,d, q, (int)np);
}

// Build a string
// - Contains n characters
// - Contains exactly p "AC" substrings.
// - Starts with a
// - Ends with b
string build(int n, int p, char a, char b)
{
    if (n == 1) {
        if (a != b) {
            return WRONG;
        }
        if (p > 0) {
            return WRONG;
        }
        return string(1, a);
    } else if (n == 2) {
        string r = string(1,a) + string(1,b);
        int q = 0;
        if (r == "AC") {
            q = 1;
        }
        if (q != p) {
            return WRONG;
        } else {
            return r;
        }
    } else {
        string res(n, '?');
        res[0] = a;
        res[n - 1] = b;
        int j = 0;
        int q = 0;
        for (int i = 0; i < p; i++) {
            //cout << "[" << res << "]"<<endl;
            while ( (j + 1 < n) 
                    && ( ((res[j] != 'A') && (res[j] != '?')) || (res[j+1] != 'C') && (res[j+1] != '?') ) 
                ) {
                j++;
            }
            if (j + 1 >= n) {
                break;
            }
            res[j] = 'A';
            res[j+1] = 'C';
            j += 2;
            q++;
        }
        for (int i = 0; i < n; i++) {
            if (res[i] == '?') {
                res[i] = 'X';
            }
        }
        if (q != p) {
            return WRONG;
        }
        return res;
    }
}

// Find the two strings with a huge brute force
tuple<string, string> solve(int k, int x, int n, int m)
{
    string A, B;
    A = WRONG;
    // for example if k=3, x=1, n=1, m=1, then we can even do s1 = A, s2 = C.
    string CHARS = "ACX";
    for (char a: CHARS) { // x 3
        for (char b: CHARS) { // x 9
            for (char c: CHARS) { // x 27
                for (char d: CHARS) { // x 81
                    for (int p = 0; 2*p <= n; p++) { //x81 x 25
                        for (int q = 0; 2*q <= m; q++) { //x81 x 25 x 25
                            if (simulate(k, a,b, c,d, p, q) == x) { //x81 x 25 x 25 x 50
                                //a good one?
                                string tem1 = build(n, p, a,b); //x81 x 25 x 25 x 50
                                string tem2 = build(m, q, c,d);
                                if (tem1 != WRONG && tem2 != WRONG) {
                                    A = tem1;
                                    B = tem2;
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    
    return make_tuple(A,B);
}

The rest

There were less than 30 minutes left when I finished problem D. The other problems were solved by few people and though problem F seemed like something I could eventually solve, I didn't think I could do it in 30 minutes. So I prefered to write this blog post. Enjoy!

It was a fun match. Although my rating shall suffer.

Sunday, December 22, 2013

SRM 601: Another slow day

At least it wasn't catastrophically bad this time.

Div1 250 - The one with apples and organges

You have `n <= 50` bags. Bag `i` has apples[i] apples and oranges[i] oranges. The process to make a valid gift involves taking exactly X fruit (`X > 0`) from each bag (The number of apples and oranges you take from each bag must be exactly X). The number of apples and oranges determines the kind of gift you make. Count the total number of different kinds of gifts you can make, that is, the total number of distinct pairs `(A,O)` where `A` is the number of apples and `O` is the number of oranges.

The trick is to notice that the value of `X` you pick uniquely determines the sum `(A + O = nX)`, which means that it is impossible to get the same pair using two different values of `X` . The problem is now about counting the number of pairs for each `X`.

Given `X`, then the number of apples you get in total uniquely determines a pair `(O = A - nX)`, so we just need to count the total number of ways to pick `A` given `X`

The next important fact is to notice that the set of valid `A` values ought to be a interval. So if you know `a_0` and `a_1` are valid and `a_0 <= a_1` , then all `a_0 <= i <= a_1` must be valid too. Then you just need to find the minimum and maximum possible number of apples. The maximum can be found by picking the largest possible number of apples from each bag. The minimum number of apples can be found by picking the largest number of oranges from each bag. Once you get the minimum and maximum number of apples, just subtract them to count the total number of ways to pick it.

long getNumber(vector<int> apple, vector<int> orange)
{
    int n = apple.size();
    int m = 2000000;
    for (int i = 0; i < n; i++) {
        m = std::min(m, apple[i] + orange[i]);
    }
    long res = 0;
    
    // For each valid X:
    for (int X = 1; X <= m; X++) {
        // If we take X, then there are nX in total.
        
        // maximum:
        int mx = 0;
        for (int a: apple) {
            mx += std::min(X, a);
        }
        // minimum:
        int mn = 0;
        for (int o: orange) {
            mn += std::max<int>(0, X - o );
        }
        // total:
        if (mx >= mn) {
            res += mx - mn + 1;
        }
    }
    return res;
}

Div1 500: The one with xor

I couldn't think of a solid solution. I did make the conclusion that you can first pick the first bit position at which the xors differ. Then the problem is about counting the number of ways to have xors: yyyyy1????? and yyyyy0?????. It seems this is the starting point of a solution, but no luck yet.

Comments?

So far it seems like an ok match. Time to write the editorial. Got to rush because I don't want to be busy with it during Christmas. ... Or do I?

Thursday, November 28, 2013

SRM 598: Dirty tricks

Another week, another Topcoder SRM.

Div1 250: The one with bins.

We have up to 50 items of different weights `100 <= "item"[i] <= 300`, we have to put these items in bins that have a maximum capacity of 300. Which means that the total weight of the items in a bin cannot exceed 300. Return the minimum number of bins we can use to store all items.

It is unusual for an input to have a lower bound. The items' weight is at least 100. This means that a bin can hold at most 3 items. More so, if a bin ever holds 3 items, all 3 items must have weight 100. So we can just treat weight 100 items separately.

There is a case: `{100, 100, 100, 100, 100, 100, 200, 200, 200}` that shows that the optimal strategy is not always to put the 100-weight items in as many bins with 3 items as possible. However, the number of bins holding 3 items is finite and at most 50/3, we can just try all possible numbers of 3-item bins that we will use. 0, 1, 2, ... `h/100` (where `h` is the number of items of weight 100). Once you do this, all of the remaining bins will contain at most 2 items. We should maximize the number of pairs of items with total capacity <= 300.

int optimal2(vector<int> item)
{
    // solve if a bin can only hold at most 2 items.
    int cost = item.size();
    sort(item.begin(), item.end());

    // Each bin can now have at most 2 items
    int i = 0, j  = item.size() - 1;

    // Maximize the number of pairs of items that can enter a single bin: 
    while (i < j) {
        // simply match the smallest item with the largest item possible:
        while ( (j > i) && (item[j] + item[i] > 300) ) {
            j--;
        }
        if ( (j > i) && (item[i] + item[j] <= 300) ) {
            cost--;
            i++;
            j--;
        } else {
            break;
        }
    }
    return cost;

}

int minBins(vector<int> item)
{
    vector<int> not100;
    int count100 = 0;
    for (int x: item) {
        if (x != 100) {
            not100.push_back(x);
        } else {
            count100++;
        }
    }
    // It is possible for a bin to hold 3 items, but they must all be 100.
    int res = item.size();
    for (int a = 0; a <= count100 / 3; a++) { // number of bins that hold 3 items
        // the rest of the bins can hold at most 2 items:
        vector<int> tem = not100;
        for (int i = 0; i < count100 - a * 3; i++) {
            tem.push_back(100);
        }
        int cost = a + optimal2(tem);
        res = std::min(res, cost);
    }
    
    return res;
}

I made plenty of mistakes while coming up to this solution. Coded a wrong solution that failed test cases (the test case I mentioned above). Coded another wrong solution that passed all test cases, but I was very suspicious about it and could notice its mistake. I had the feeling this problem was going to have many wrong solutions and I was right. Unfortunately, during the challenge phase I remembered I am unable to read other people's codes. Many of the codes seemed overly complicated and possibly wrong, but I had no idea how to find a good case for them.

Div1 550

It seemed complicated, I eventually decided to test my luck and skip the div1 550 to open the hard problem.

Div1 950: The one with cities

You have a country/set of n (at most 50) cities. The cities have connections and these connections makes the graph of cities a tree (how suspiciously convenient). The plan is to put beacons in some of the cities so that, when evaluating the list of distances from a city to each of the beacons, the distances make a sequence that is a unique identifier for the city.

A basic `O(2^n)` solution would be to just try each subset of cities and place beacons on it and pick the smallest subset that allows the unique identification to work. It occurred to me that with backtracking and some cleverness, maybe we can optimize this solution to run fast enough and give a correct answer at least most of the time. I expected it to be quite hard to make system tests for this problem.

So I made some backtracking algorithm. All cities begin with beacons on them and we decide from city 0 to n-1 whether or not we remove the beacon from it. It is only possible to remove a beacon if all the remaining beacons allow the identification. Note that when all cities have beacons, the identification works just fine, because each city is the only city with a distance of 0 to its beacon. When doing the backtracking, make sure not to remove a beacon if it no longer allows identification of cities.

The backtracking wouldn't run in time. But my dirty trick idea was to make the backtracking search stop after a high number of steps. So that we use 2 seconds of time to do the search and return the best result we found in those limited 2 seconds.

It wasn't good enough, what if the optimal set of beacons to remove happens to be a set of the last indexed items? My new idea was to make this an unlikely event, my algorithm would randomly modify the positions of the input cities, lowering the chances of a crafted test case of occurring. Now, this approach would be wrong if there was a case that required a perfectly unique set of beacons to be removed, so the probability would be low. I had another trick idea: Spend half of the time in a greedy idea, giving priority to nodes with little edges so we can remove them.

Implementing the dirty tricks was not easy and took me quite a while. So much that in fact the only code I could submit was incomplete. It only did the randomized idea and it still only allocated half of the execution time for it. The randomized idea seemed to be returning a wrong result for a large case I had, so I sort of suspected it would either fail in the challenge phase or in the system tests.

Unfortunately, that didn't happen, and my very dirty brute force algorithm passed system tests. Good for my rating.

Comments?

Wednesday, November 20, 2013

SRM 597: argh

The other day I accepted to write editorials to the TCO. Unfortunately they never warned me that some of the problems I would read could end up getting used outside of the TCO. I only learned that after the problems were sent to me. Had I know that I could miss SRMs just for helping with these editorials, it would have been a deal breaker. I hate missing SRMs. I really hate missing SRMs.

I had hopes that maybe if one of the problems I read gets used like this in a SRM, I could at least have a chance to write the other problems in the SRM. Nope. Then I tried to maybe get added as a tester to read the other problems so I could at least have progress in the editorial. Nope. So missing this SRM was useless. Week ruined.

Div1 600: ConvexPolygonGame

This is the problem that ruined my week. Two players take turns. It all starts with a convex polygon. In each turn, the player draws a polygon such that:

  • The previous polygon contains all the vertices of the new polygon.
  • The vertices of the new polygon are lattice points
  • No three vertices are colinear.

The player loses when it is impossible to draw such a polygon.

The trick is to notice that as long as a player can make a move, the player can always pick a minimal triangle that will win the game. We know that the game will end with a single triangle. Imagine this triangle came after a sequence of moves. This triangle will then be inside each of the polygons used in the game, including the first one. Then first player can just directly use this triangle and win.

So we just need to check: Is there at least one triangle with non-zero that can be drawn in the first turn? If so, first player draws a terminal one and wins, else the first player loses.

So far it is a cool little problem. The original version planned for TCO had `(-500 <= x,y <= 500)` constraint for the vertices of the first polygon. Which had a couple of cute solutions, for example, you could iterate through all the points inside the polygon and if you ever find a point that is not colinear to the previous two points, the job is done. Unfortunately, when migrating it to SRM format, they changed the problem by making the constraints much larger. So the clever conclusion that you only need to check if one drawable polygon exists becomes just problem obfuscation, because the real issue is to work up the implementation problem of checking for that condition. Apparently the idea is to use only the points that are close to the vertices, within 200000 distance or something like that.

I think this problem was a decent TCO finals easy with the small constraints. With the large constraints though, it becomes a SRM div1 medium with excessive implementation that doesn't make it more interesting.

Friday, November 01, 2013

SRM 596: Lack of speed will kill you

Well, that it is , a not great day because I took too long to solve 250 points and the other problems seem too hard. Starting this at 11:55.

Div1 250: The one with sequences

You start with a sequence of n zeros. You want to convert it into a desired sequence. There are two allowed moves: a) Increment a single element by 1. b) Multiple all of the elements by 2. The elements of desired are non-negatives less than 1001.

I noticed right away that you can first decide the number of double operations because you will need at most 9 (Multiply 1 by 2 ten times and you get 1024). So the rest is an easy subproblem: For each element in the desired sequence, find the minimum number of increments you need if you are going to multiply by 2 a fixed number of times. I used dynamic programming, maybe there is an easier method.

static const int MAX     = 1000;
static const int MAX_N   = 50;
static const int MAX_LOG = 11;
static const int INF     = MAX_N * MAX;

vector<int> desiredArray;

int mem[MAX + 1][MAX + 1][MAX_LOG + 1];
int rec(int x, int y, int d)
{
    int & res = mem[x][y][d];
    if (res == -1) {
        res = INF;
        if (x == y) {
            res = 0;
        } else {
            //increment
            if (x  + 1 <= y) {
                res = 1 + rec(x + 1, y, d);
            }
            //double
            if ( (2*x <= y) && (d > 0) ) {
                res = std::min(res,  rec(2 * x, y, d - 1) );
            }
        }
    }
    return res;
}

int getMin(vector<int> desiredArray)
{
    int res = INF;
    memset(mem, -1, sizeof(mem));
    for (int d = 0; d <= MAX_LOG; d++) {
        // If we do the double operation d times, what is the minimum number
        // of increment moves each element needs?
        int m = d;
        for (int x: desiredArray) {
            m += rec(0, x, d); 
        }
        res = std::min(res, m);
    }
    return res;
}

I took way too long to code. Everyone in the division summary had far better scores than I.

Update: During the challenge phase, I noticed that the answer is equal to the maximum number of bits (if not 0) plus the sum of 1 bits. Makes sense. I wish I coded my dp faster.

Div1 500: The one with bits

This problem strikes me as having too many complicating factors. I am starting to think that it is a brute force problem. Right now trying to think of a way to do that, but with 13 minutes left I doubt I will be able to do anything. My 250 score is too low, my only hope is that there is a greedy solution to 250 that most people are usuing and it is wrong, but I doubt that to be the case. I think the "Fix number of doubling" solution is straightforward to see and can't think of anything else.

Programming this post to appear exactly as the match ends.

Update

Now they are gonna have an update. This is the kind of matches that makes me hate the 250-500-1000 strategy. Putting two greedy problems in the first two slots ensures that even if I get to solve them, it will be low score and a wasted match. At least focusing on 1000 would be more interesting, and a low score in a match where everyone solve 2 problems is as disastrous for rating as a zero score anyway.

Wish I could opt out of writing the editorial ( I cannot ), also without practice rooms it will delay development of the editorial too. Just awful.

Friday, October 04, 2013

Codeforces #204 (div 1) Recap

So, I could only solve 2 problems and I am not entirely sure of them.

Problem A - The one with rounding

Link to problem statement

The first thing is to notice that all numbers are guaranteed to have exactly three decimal points. From now on. we can just care of the decimal part (x[i] will be number from 0 to 999), and minimize the absolute value of the difference.

  • If x[i] is 0 (So it was a number that ends with .000, like 12.000), then no matter what we do, it will contribute 0 to the difference.
  • Else if we choose to round down the number, the difference increases by (-x[i]) / 1000.0 .
  • If we choose to round up, then the difference changes by (1000 - x[i]) / 1000.0.

n numbers have to be rounded up. If i of those numbers are zero, then the total difference will be: (1000 * (n - i) - sum(x[i]) ) / 1000.0. In other words, the only thing that determines the difference is the number of zeros that we round up. We can just brute force for this number and pick the best difference we find

// input:
string a[4000];
int n2; // n in the statement

// other:
int x[4000];

double solve()
{
    int n = n2 * 2;
    for (int i=0; i<n; i++) {
        istringstream st( a[i] );
        int z=-1, f=-1; char ch;
        st >> z >> ch >> f;
        x[i] = f; // only the modulo , the decimal digits matters
    }
    int res = 5000000;
    int z = count(x, x + n, 0);     // number of zeros
    int sum = accumulate(x, x+n, 0); // total sum
    
    // how many zeros will be ceil?
    for (int i = 0; i <= z && i <= n2; i++) {
        res = min(res, abs( (n2 - i) * 1000 - sum ) );  
    }
    // divide result by 1000
    return res / 1000.0;
}

During the match I first tried a badly-thought greedy algorithm. I even tested it comparing it with a bruteforce algorithm before submitting, but it still failed, apparently the bug with the lame greedy needed very specific input. But while coding the greedy algorithm I noticed the -x[i] and 1000-x[i], which helped me solve the problem correctly (assuming it passes)

Problem B - The one with permutations

Problem statement

The first thing I noticed was that result for the second example was a integer, that is too much of a coincidence. Somehow I decided that maybe the result is equal to (number of steps needed to sort the permutation without the random stuff) * 2 - 1. It turns out the estimation was wrong, but the idea was almost correct (I think).

Anyway, assume you have to do X steps in order to solve it correctly. After you make a step, your friend will throw a coin, and there is a 50% probability he will help you and a 50% probability he will make things worse. It appears to me that he helping you will always decrease the number of required steps by 1, and he not helping you will always increase the number of required steps by 1 (This in addition to the step you performed). The result is the following recurrence, 'f(x)` where x is the number of required steps.:

`f(0) = 0`
`f(1) = 1`
`f(x) = 2 + f(x-2)/2 + f(x) / 2`
`f(x) = 4 + f(x-2)`

We can just fill the recurrence iteratively.

int n;
int p[3000];


double solve()
{
    //a) cost to sort
    int res = 0;
    for (int i = n - 1; i >= 0; i--) {
        pair<int,int> mx = make_pair(-1, -1); 
        for (int j = 0; j <= i; j++) {
            mx = std::max(mx, make_pair(p[j], j));
        }
        res += i - mx.second;
        for (int j = mx.second; j < i; j++) {
            p[j] = p[j+1];
        }
        p[n-1] = mx.first;
    }
    // b) Recurrence:
    double dp[3];
    dp[0] = 0;
    dp[1] = 1;
    for (int i = 2; i <= res; i++) {
        dp[i % 3] = 4 + dp[ (i + 1) % 3 ];
        cout << i << " ; " << dp[i % 3] << endl;
    }
    
    return dp[res % 3];
}

Problem C : The one with parenthesis and cycles

Statement

I didn't have much time to code it (less than 10 minutes), but I think I found a solution.

It appears to me that the best parenthesis sequence has to be cyclic because the cost function is cyclic. When n is even, it should be of length n, when n is odd, it should be of length 2n. Since m is guaranteed to be even, we can half it.

So we just need to generate the cyclic parenthesis sequence. The cost of the sequence is easy to find because the costs are cyclic. Constraints are large in the case n is odd. But I am pretty sure we can use a meet in the middle approach so that the process is `O(2^n)`.

Friday, September 27, 2013

SRM 592 - sloow

d1 300 - The one with colors

So you have a sequence of balls, each has one of three colors: red, green and blue. You need to place the balls in a row. The order in which to place the balls is given by a string S. When you place a ball, you get points equal to the number of different colors to its right + the number of different colors to its left. Find the maximum possible score.

At first I thought of a dynamic programming solution. It was correct and all, but perhaps I should have waited to think of something better.

Let us name two sides of the row, the left and right side. When we place a new ball, it is best to place it between the two sides. So the score of that step is equal to the number of different colors in the right side + the number of different colors in the left side. The trick is that after adding this ball, we decide whether it belongs the left or the right side. In my dynamic programming solution I simulated both possibilities, but it is not needed. It is always better to add the ball to a side that doesn't already contain its color (This decision will increase the score of following steps by 1, any other decision won't). If both sides contain the color, it doesn't really matter.

int getNumber(string s)
{
    set<char> s1, s2;
    int res = 0;
    for (char ch: s) {
        res += s1.size() + s2.size();
        // if s1 already contains s[i] insert it to s2:
        ( s1.count(ch) ? s2 : s1).insert(ch);
    }
    return res;
}

There are other ways to implement the same logic. For example, in step i, the maximum score you can get is equal to min(2, number of red balls already placed ) + min(2, number of blue balls) + min(2, number of green balls). But the resulting code is actually more complicated.

d1 500: The one with permutations

Given `k` and `n` , count the number of pairs of permutations `(A, B)` of length `n` such that `sum_(i=1)^(i<=n)( max(A_i, B_i) ) >= K`

I spent most of the match trying to come up with a way to divide this problem into workable sub-problems, I sort of knew it would be a dynamic programming solution, but I had no idea how. I was trying and trying to find a way to simplify sub-problems. Oh well.

The rest

Opened div1 hard. I think it will be an approachable one once I get help. Tried to challenge but there was only one suspicious solution to div1 250 in my room and it got challenged before I could think of something.

Tuesday, August 27, 2013

SRM 589: Read the statement! (upd)

So another SRM. This time the scores were very special: 250-450-900. It seemed like a good match to use my strategy. At the end though, I didn't take much advantage of it, because of two blunders...

Div1 450: The one with gears

We have gears of three colors, red, green , blue. Initially, some pairs of gears are meshing: If one of the gears in a meshing pair turns in a direction, the other gear must turn in the other direction. No two gears of the same color ever mesh. We want to be able to turn all the gears in such a way that all the (remaining) gears of the same color turn in the same direction. What is the minimum number of gears to remove?

I think using clock-wise and anti-clockwise for the directions is too verbose, so let us call say that some gears are positive and some gears are negative. Now all the gears of the same color must have the same *sign* and two connected (meshed) gears must have different signs.

There are only three colors and two signs, so how about we do brute force for the signs? There will always be two colors with the same sign, otherwise it doesn't matter which sign. So two colors have equal sign, let us say positive, and another color is negative. Between the two positive colors, there should be no connections...

We can actually ignore the negative gears, all their connections are already valid and removing them won't fix anything. So now we only have gears of two colors that should never be connected. This is the independent set in a bipartite graph. So let us just run max flow...

During the match, I took a bit of time because at first I was considering the negative gears, causing a single wrong case (the other example cases were very weak). It was early in the morning, I was just confused...

Div1 900: The one with bits

You want a binary string of N bits (N is up to 300) to be one in which the prefix of length N-M and the suffix of length N-M are equal. You can flip a single bit at cost 1 and you can also flip the first K*M bits at cost 1 (for any positive K). What is the minimum cost?

I think I have some ideas. Unlike most div1 hards, I think I can solve this in a few hours and without help. It is a dynamic programming problem with complications.

Div1 250: The one with palindromes

Turn a string palindrome (again?). This time your only allowed move is to pick two alphabet letters X and Y , and turn all the X letters into Y. Return the minimum number of letter positions you need to change.

I only had 10 minutes, so I rushed to code a solution, which was mostly right. I missed the fact that you want to minimize the number of changed positions (it didn't help that this fact was disguised by some talk about seconds). Not the number of changed letters. I only noticed when there were some seconds left before the end of the challenge phase. I fixed the code some time before the end of intermission.

Anyway... The palindrome requirement means that some pairs of positions must be equal. This means that at the end the letters in that pair of position must be equal. This creates a relationship/connection between pairs of letters. At the end, all letters in a group of letters that are connected (directly or indirectly) must be equal. We have to change each of these letters to the same letter. It is best to pick the letter that appears the maximum number of times. Repeat for each connected component.

int getmin(string S) 
{
    int n = S.length();
    vector<bool> visited(26, false);

    // This DFS finds a list of all letters that are *connected* to letter ch:
    // Returns the maximum number of times one of the connected letters appears
    function<int(char)> dfs = [&](char ch)->int {
        if (!visited[ch - 'a']) {
            int res  = count(S.begin(), S.end(), ch);
            visited[ch - 'a'] = true;
            // Two letters are connected if the palindrome rule states that
            // two positions that contain the letters must be equal:
            for (int j=0; j<n; j++) {
                if (S[j] == ch) {
                    res = std::max(res, dfs( S[n-j-1] ) );
                }
            }
            return res;
        }
        return 0;
    };

    // For each group of connected letters, find the letter that appears the
    // maximum number of times, subtract it from the total cost:
    int res = S.length();
    for (char ch = 'a'; ch <= 'z'; ch++) {
        res -= dfs(ch);
    }
    
    return res;
    
    
}

Update Maybe that lambda stuff makes the code appear complicated, here is a python version:

def getmin(S):
    n = len(S)
    visited = set()
    
    # This DFS finds a list of all letters that are *connected* to letter ch:
    # Returns the maximum number of times one of the connected letters appears
    def dfs(ch):
        if ch not in visited:
            visited.add(ch)
            res = S.count(ch)
            # Two letters are connected if the palindrome rule states that
            # positions that contain the letters must be equal:
            for i in range(0,n):
                if S[i] == ch:
                    res = max(res, dfs( S[n - i - 1]) )
            return res
        return 0
    
    # for each connected component, subtract the letter with the maximum number
    # of position: Call dfs for each of the letters in string.lowercase :)
    return n - sum( dfs(ch) for ch in string.lowercase )

Outcome / Comments / etc?

So I am back to yellow, I could have gotten much better rating if I paid attention when reading the 250.

What did you think about the match?

Monday, August 12, 2013

SRM 588: ouch (Update)

As I write this, I have to go, I am scheduling this to be up the time the challenge phase ends.

Div1 medium: The one with keys

There are up to 12 rooms, each with some red and green locks (up to 10 of each kind). You can use red or white keys to open red locks and green or white keys to open green locks. Each key is consumed once you use it. Each room has keys inside. What is the maximum number of keys you can have?

I was at first trying to make a viable dynamic programming solution. Similar to a GCJ problem. But I was not able to optimize it.

I eventually settled for a brute force search. Whenever you decide to open a room, you should only use white keys if you NEED to. This approach was slow, so I optimized it using all the dirtiest tricks in the book. Let us see if it passes.

Update: Turns out it passes system tests, so I will explain it.

Try all permutations of the order in which you open the rooms.

If you decide to enter a room and you have enough red and green keys, then you simply shouldn't use white keys. You should always use as little white keys as possible. Turns out this is also the "key" to solve it using dynamic programming.

With this, your approach needs `O(n!)` time, 12! is high, but not very high. So we can try some optimizations.

Imagine that at a given moment, there is one room that can be opened and after opening it, you end up with at least as many keys of each type as before. Then you should always open this room. So you shouldn't try sequences in which this room isn't opened.

Another special case, there is a room that you can open, but after opening it you end up with less keys of each type. This room is never a good idea. Ignore it.

I think that the two previous optimizations are enough , I was about to submit this, but last second I decided I could do an extra optimization, give priority to moves that give the maximum number of total keys, and cut the search when we tried too many options. I am not sure this is necessary, but I put it just in case.. Update 2: Yeah, it turns out this optimization wasn't needed.

Before the match I found out std::function causes a bug in TopCoder. That was too bad, because since there is no sane way to have anonymous recursion in c++ without using it, I had to use an outside function for the backtracking. This meant I had to copy all 5 argument variables as class members. What a bore.

vector<int> doorR,doorG,roomR,roomG,roomW;
int n, best, options[12];

void rec(int p, int r, int g, int w)
{
    best = std::max(best, r + g + w);

    if (p < n) {
        //each tuple is (i, nr,ng,nb):
        tuple<int,int,int,int> results[n-p]; //possible options
        int t = 0;
        
        for (int i=p; i<n; i++) {
            // for each candidate options[i], find if it is possible,
            // save it in results
            int &x = options[i];
            int nr = r - doorR[x];
            int ng = g - doorG[x];
            int nw = w;
            if (nr < 0) {
                // Not enough red keys, use white keys
                nw += nr;
                nr = 0;
            }
            if (ng < 0) {
                // Not enough green keys, use white keys
                nw += ng;
                ng = 0;
            }
            if (nw >= 0) {
                // if the number of white keys is non-negative we can do it
                // Increase the number of keys
                nr += roomR[x];
                ng += roomG[x];
                nw += roomW[x];
                if ( nr >= r && ng >= g && nw >= w) {
                    // This move is always a good idea, we should do it:
                    swap(options[p], options[i]);
                    rec(p+1, nr,ng,nw);
                    t = 0;
                    swap(options[p], options[i]);
                    break;
                } else if ( nr > r || ng > g || nw > w) {
                    // Make sure the move is a good idea before adding it
                    results[t++] = make_tuple(i,nr,ng,nw);
                }
            }
        }
        // process tuples:
        for (int j=0; j < t; j++) {
            int i, nr,ng,nw;
            tie(i, nr,ng,nw) = results[j];
            swap(options[p], options[i]);
            rec(p + 1, nr, ng, nw);
            swap(options[i], options[p]);
        }
    } 
}

int maxKeys(vector <int> doorR, vector <int> doorG, vector <int> roomR, vector <int> roomG, vector <int> roomW, vector <int> keys)
{
    // copy stuff, this is tedious:
    this->n = doorR.size();
    this->doorR = doorR;
    this->doorG = doorG;
    this->roomR = roomR;
    this->roomG = roomG;
    this->roomW = roomW;
    best = 0;
    for (int i=0; i<n; i++) {
        options[i] = i;
    }
    rec(0, keys[0], keys[1], keys[2]);
    return best;
}

Div1 easy: The one with songs

You have T time available to play as many songs as you can. Each song has a duration and a tone. If the tones of two consecutive songs are `x, y` then you need to rest `abs(x-y)` time units before playing the second song. What is the maximum number of songs?

It took me too long to correctly code this solution. As usual, I waited till there were less than 10 minutes before opening this problem.

The trick is, let us say you decide to play some songs. If the minimum tone is `p` and the maximum tone of the songs you play is `q`, then the minimum time you will need to wait because of the tone is always `q - p`. So, let us pick the minimum and maximum tone of the songs you will play, `O(n^2)` options for this pair. The rest is just to play the best song durations between these tones such that the total duration is less than or equal to `T - q + p`.

int maxSongs(vector <int> duration, vector <int> tone, int T)
{ 
    //sort the tones:
    int n = tone.size();
    for (int i=0; i<n; i++) {
        for (int j=i+1; j<n; j++) {
            if (tone[j] < tone[i]) {
                swap(tone[j], tone[i]);
                swap(duration[j], duration[i]);
            }
        }
    }
    int res = 0;
    // pick the minimum and maximum tone:
    for (int a=0; a < n; a++) {
        for (int b=a; b < n; b++) {
            int t = T - tone[b] + tone[a];
            int c = 0;
            // save the durations in a vector
            int tim = 0;
            vector<int> d;
            for (int i=a; i<=b; i++) {
                d.push_back(duration[i]);
            }
            // sort the durations, so that we pick the smallest c durations:
            sort(d.begin(), d.end());
            for (int x: d) {
                if (x <= t) {
                    t -= x;
                    c ++;
                }
            }
            // remember the best
            res =std::max(res, c);
        }
    }
    return res;
}

Monday, July 01, 2013

Today's TopCoder Test SRM

So I wanted to take this SRM seriously as it would be good practice. It didn't help that I remembered to have solved two of these problems before. But there we go.

I wanted really hard to try the new c++ features, but there wasn't that much of a chance to do it, I still managed to pull some tricks.

RPSTournament

Problem statement

I didn't remember this problem. I likely didn't see it before. This old problem had a very confusing problem statement (So a competitor defeats people with both larger and smaller seeds within range? But then what decides which of the two competitors wins? I gave up.

WebsiteRank

Problem statement

I remembered solving this problem before, but I of course don't have such a precise memory to remember the solution. I did remember that after SRM 357, there were tons of discussion about how trivial Floyd-Warshall passed (There can be around 800 website names.)

I actually had difficulty with the problem because I initially read the statement wrongly. The statement actually tells you to ignore points from pages that link to a site, if the site directly or indirectly links to them. I initially understood it as making the website ignore only the points "that come from it".

Once you read the problem correctly. You should be able to use Floyd-Warshall to quickly find which sites link directly or indirectly to each website. Once you ignore links between sites that do this - You are basically removing cycles from the graph. The graph becomes a directed acyclic graph. You could top-sort it, but it is not needed to. The total number of points each website receives is equal to the total number of paths that finish in the website (which is easy to calculate, even with a Floyd variation, because the graph is acyclic).

If the 800 website names bother you, just notice that there are only at most 51 relevant website names. (Websites that are the query's website or websites that are linked by other websites), so you can ignore the other websites and just consider them as extra initial points for each website. So in addition to counting the number of paths, you multiply the number of paths between each website i to the query website by the number of initial points of i.

// Using long (instead of long long) without a define!
long countVotes(vector <string> votes, string website)
{
//maximum number of website names is around 50/3 * 50 ~= 833
int n = votes.size(), t = 0;
map<string,int> websiteId;
vector<vector<string>> data;

// Convert the votes array of strings to a data[][] 2d array of a
// vector of strings. Note the auto keyword:
for (auto q = votes.begin(); q!=votes.end(); q++) {
istringstream st(*q);
string x;
// Look at this!:, pushing an empty vector:
data.push_back( {} );
auto & v = *data.rbegin();
while (st >> x) {
v.push_back(x);
}
if (websiteId.count(v[0]) == 0) {
websiteId[v[0]] = t++;
}
}
if (websiteId.count(website) == 0) {
websiteId[website] = t++;
}
vector<long> initialVotes(t, 1);

int adj[t][t]; //Adjacency matrix
int reach[t][t]; //Direct or indirect adjacency matrix (After Floyd-Warshall)
long ways[t][t]; //Number of paths between each pair of nodes)
memset(adj, 0, sizeof(adj));
memset(reach, 0, sizeof(reach));
memset(ways, 0, sizeof(ways));
// First, setup the adjacency matrix:
for (int i=0; i<n; i++) {
for (int j=1; j<data[i].size(); j++) {
if (websiteId.count( data[i][j] ) == 0) {
initialVotes[i]++;
} else {
int u = websiteId[ data[i][j] ], v = websiteId[ data[i][0] ];
reach[u][v] = 1;
adj[u][v] = 1;
}
}
}
// Find what nodes can be reached indirectly:
for (int k=0; k<t; k++) {
for (int i=0; i<t; i++) {
for (int j=0; j<t; j++) {
reach[i][j] |= (reach[i][k] & reach[k][j]);
}
}
}
// Now count the number of paths between each pair of nodes:
for (int i=0; i<t; i++) {
for (int j=0; j<t; j++) {
if (adj[i][j] && ! reach[j][i]) {
ways[i][j] = 1;
}
}
}

for (int k=0; k<t; k++) {
for (int i=0; i<t; i++) {
for (int j=0; j<t; j++) {
ways[i][j] += ways[i][k]*ways[k][j];
}
}
}

// Done and multiply:
long c = initialVotes[ websiteId[website] ];
for (int i=0; i<t; i++) {
c += initialVotes[i] * ways[i][websiteId[website]];
}
return c;
}

It seems I took longer to solve this problem than back in 2007, but this time I got the problem correct. I failed system tests in 2007.

PalindromeMaker

Link to statement

I still used my new strategy of waiting till there are less than 10 minutes before the end of the match before opening the easy problem.

This problem gives you a string of upper case letters. Return a palindrome permutation of those letters. If there are multiple solutions, return the lexicographically first one. If there are no solutions, return "".

Forget about the lexicographical condition. If we were to take any string like AABB and turn it into a palindrome, we would usually need each letter to appear an even number of times, so you can do ABBA or BAAB. There is one exception, when the length of the string is odd, then exactly one of the letters must appear an odd number of times. This letter should be used in the middle position.

Let us now deal with the lexicographically-first decision. Try each of the pairs of repeated letters. It is always better to place the lexicographically-smallest letter of those available in the smallest available position. So, for each letter from A to Z, in that order, pick every two of that letter, and push one to the left end and another to the right end. Remember the letters that appear an odd number of times. Then we can put the left, right (and maybe middle) ends together.

string make(string baseString)
{
string left, right, odd;
for (char ch='A'; ch <= 'Z'; ch++) {
// Count the number of times letter ch appears:
int c = count(baseString.begin(), baseString.end(), ch );
// If it is odd, save it as the odd letter:
if ( c % 2 == 1) {
// Don't allow more than one odd letter:
if (odd != "") {
return "";
}
odd = ch;
}
// Half goes to the left end, other half to the right end.
string rep = string( c / 2, ch );
left += rep; right = rep + right;
}
// An odd letter must exist if and only if the string has odd length:
if ( (baseString.size() % 2 == 1) == (odd != "") ) {
return left + odd + right;
} else {
return "";
}
}

The end

It was an ok match, old problems feel very easy in comparison to current contests. Six years! That is depressing...