One Point Solution

  • Subscribe to our RSS feed.
  • Twitter
  • StumbleUpon
  • Reddit
  • Facebook
  • Digg

Friday, 13 May 2011

Some thoughts after SRM 506.

Posted on 09:38 by Unknown
It was good match for me. This is officially the first time I am actually able to solve a max flow problem during the match. I also gained 72 points and am back to 1900-2000 range in topcoder. However, I made many blunders and was victim of past hindsight.

Note 1: Know your STL.
You can find the following in my code for div1 600:

// If you know a better way to append a 0 to the beginning of
// this, I am listening...
reverse(districts.begin(), districts.end());
districts.push_back(0);
reverse(districts.begin(), districts.end());


I knew I had to insert a 0 to the beginning of the vector<int> but I could not think of a quick STL way to do it. I was later able to find it, the (actually intuitive for a STL feature):




districts.insert(districts.begin(), 0);

It is a little strange it needs an iterator when it is a non-static method. As always with the STL you will type the same thing more times than needed.

Note 2: Don't code, think!
At one point of the code, you need to get the time to move from a district i to j using no cars and also to calculate the time using each of the available cars. Coding this is actually where I spent most of the 58 minutes I used to solve this problem. The reason is that I, for some reason decided to use a Bellman-Ford (instead of Dijkstra) that starts at district i, may decide to pick the car (if it exists) and then goes to district j. In total it is
a complicated minimum path problem in which the state has two dimensions for its vertices (district, are we inside car?) and thus the transitions are also complicated to code. After using the car, the cost uses the inverse drive velocity instead of the walk velocity.

The one blunder here was to rush into coding that Bellman-Ford. It was better to just stop for a second, and note that you can multiply inverseWalkSpeed or inverseDriveSpeed after the minimum distance is calculated instead of during. What this means is that if you
just had precoded a dist[x][y] matrix that yielded the minimum distance between districts x and y. Then the minimum time without using a car is:
dist[x][y]*inverseWalkSpeed and the time using a certain car z that is in district c is simply: (dist[x][c]*inverseWalkSpeed + dist[c][y]*inverseDriveSpeed). This helps because the dist array is very easy to generate by using Floyd-Warshall on the original road cost matrix (the one in the input).

A similar issue is with the max flow part. In my original analysis, I first calculate the total cost without using cars, and then I make a max-benefit matching to maximize the reductions in cost (for each pair (transition, car) calculate the reduction in cost between not using any car and that option). Max benefit is the same as min cost when the cost is negative, and although it is possible to do that in min cost max flow, the implementation is harder (you need Bellman-Ford instead of Dijkstra for the first iteration, then stick to slow Bellman-Ford or do a trick with "potentials" to use Dijkstra. The min-cost-max-flow algorithm can be done much simpler without negative costs.

Instead of diving into negative costs that quickly, I could have tried to get rid of the negative part. Which is perfectly possible. Just include the cost to use no car and the costs to use each car in the network. Not using any car should have infinite capacity, alternatively, just connect each transition directly to the sink with capacity 1 and cost = cost of normal travel. Either way, what follows is what my code could have been if I stopped to improve the analysis of the problem instead of just starting to type quickly:


int travel(vector <int> cars, vector <int> districts, vector <string> roads,
int inverseWalkSpeed, int inverseDriveSpeed)
{
t = roads.size();
iws = inverseWalkSpeed;
ids = inverseDriveSpeed;
this->roads = roads;

districts.insert(districts.begin(),0);

int n = districts.size()-1;
int m = cars.size();

// Floyd-Marshall to get the minimum distances.
int dist[t][t];
for (int i=0; i<t; i++) {
for (int j=0; j<t; j++) {
dist[i][j] = roadCost(i,j);
}
}
for (int k=t; k--;) {
for (int i=t; i--;) {
for (int j=t; j--;) {
dist[i][j] = std::min(dist[i][j], dist[i][k] + dist[k][j] );
}
}
}

network * G = new network;
for (int i=0; i<n+m; i++) {
G->addVertex();
}
G->sink = G->addVertex();
G->source = G->addVertex();
for (int i=0; i<n; i++) {
int u = districts[i], v = districts[i+1];
G->addEdge(G->source, i, 1, 0);
for (int j=0; j<m; j++) {
//Time to travel from u to v using car j:
int costUsingCar = dist[u][cars[j]]*iws + dist[cars[j]][v]*ids;
G->addEdge(i, j+n, 1, costUsingCar );
}
//Time to travel from u to v not using any car:
G->addEdge(i, G->sink, 1, dist[u][v]*iws );
}
for (int j=0; j<m; j++) {
G->addEdge(j+n, G->sink, 1, 0);
}

int flow; long long cost;
G->minCostMaxFlow(flow, cost);
delete G;

assert(flow == n);

return (int)(cost);
}


It is a lot more concise than what I submitted during the match.

Mantain your own code library.
I had to use min-cost max flow to solve this problem. It is a particularly complicated algorithm and for that reason I use a library code. Unfortunately, It seems had not updated nor used that code in years. It seems that the last few years I have only used min-cost-max-flow in editorials and problems of my own, and that means Java. I could not have used my Java implementation either because I needed negative costs (thanks poor analysis!). So, I used the c++ code I've written before.

The horror. It seems that back when I wrote that code, I was a lot less concise, and also liked code hacks like avoiding the use of {} brackets when not necessary (That is silly, they are ALWAYS necessary, else it will take you more time to update the code after you want to add lines to your if-then-else that only used one line... =) . Worse, it was particularly abusive of the >? <? g++ extensions (Very useful min and max operators, that were removed from more modern versions of g++). So I could not compile the code locally. After thinking that I should not waste time redoing such code, and remember that the VERY OLD g++ version in Topcoder's server does support those g++ extensions. I decided to switch to manual compilation and tests using the compile and test button from the arena. But that turned out to be very slow, specially because I had to correct some syntax errors when building the network.

Focus, please focus
I finally implemented the min-cost max flow. And the sweetest thing happened. All results were wrong. I was getting 44 instead of 36. I knew that the normal cost without using any car is 40, so the min cost flow should have returned -4. So, what happened? I came to conclude that, unlike what I remembered about my precoded min cost flow solution, it did not support negative numbers. Panic. So, what was I supposed to do. I needed negative costs (I thought I did, but it turns out it was not true) and I had no min cost flow implementation that solves it.

Then I remembered that I had a Hungarian algorithm code lying around, since I was doing min-cost Bipartite matching, that was actually a useful thing. The problem is that my Hungarian algorithm code was much older and I did not remember how to use it... I was in the process of analyzing it, when I noticed something funny...

When I was coding the thing that uses min-cost max flow. I was multiply the costs by -1, because that is what you do. But then, the returned cost would be -BENEFIT. But I was doing MaxBonus = result of cost flow. And finally (total - result). Do you see it? I was multiplying by -1 twice.

I tried, I really tried to restore the solution to when the min-cost-max-flow algorithm was implemented. But Kawigi Edit's undo limit turned out to be smaller than I needed. I had to reimplement min-cost-max-flow, again, and also using manual compilation and examples because local tester did not work, again.

Mantain your code library, really
It also does not hurt if you left some documentation comments regarding how to actually use your code. Just because you wrote it, it does not mean you won't forget how to use it after 5 years. You should also practice more and make sure to keep your library code clean and to practice using it. After changing your local compiler's version, make sure your personal library of code for contest actually compiles with it.

It does not hurt to try to simplify and minimize the size of your pre-made code. Because when you actually get to use it, and it makes your code look like a Behemoth, it is very embarassing.


Rule #1, again
Smaller issue, I had a failed challenge, which as you may remember from very old blog posts, breaks my topcoder rule #1. Do not challenge. (Rule #2 is DO NOT challenge). The solution I challenged was correct, for some reason It seemed wrong to me. I even tried it mentally and thought that the case I provided would make it case, that was not true. This was a unnecessary risk, if I failed any of my solutions to the problems, I would have gotten a very bad score because of this failed challenge.
Email ThisBlogThis!Share to XShare to Facebook
Posted in srm, stl, topcoder | No comments
Newer Post Older Post Home

0 comments:

Post a Comment

Subscribe to: Post Comments (Atom)

Popular Posts

  • TopCoder SRM 557 - finally
    SRM 557 Explanation for division 1 Easy and match recap. Explanations for div2 easy and div2 medium. It feels like it has been ages since t...
  • SRM 589 Editorial
    I have finished writing the editorial for TopCoder SRM 589: http://apps.topcoder.com/wiki/display/tc/SRM+589 . As you most likely noticed. L...
  • SRM 590 recap and editorial
    Another week another Topcoder match. Not a great day. I had a bad flu and still do. Div1 500: The one with Xor Given a list of cards with nu...
  • SRM 546: relief
    I figured I should post something about this SRM. I've been very busy these weeks because the semester is ending and I tried to win a t-...
  • SRM 526: The killing wait for results
    While I wait for results, here is my perspective on this algorithm contest. It began with issues, it had to be postponed 15 minutes. TC has ...
  • SRM 554 div1 hard: TheBrickTowerHardDivOne
    Link to problem statement We got infinitely many bricks of dimensions 1x1x1 and C different colors. Count the number of towers of size 2x2...
  • SRM 533: Div1 500 MagicBoard explanation
    Finally solved it. It is a nice problem that is worth explaining in a post. You have a grid/board of at most 50x50 cells. Some cells contain...
  • Member SRM 505: Part 1
    So, let me explain a couple of problems from a Topcoder Member SRM that I wrote and never got an editorial. BTW, it was the last member SRM....
  • ListedLinks 2012-02-10
    Saturday Morning Breakfast Cereal comics: Grace Hopper's ghost That Oracle engineer blog post Oracle would really not like anyone to se...
  • 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....

Categories

  • acm
  • algorithm
  • answers
  • arenaplugin
  • badday
  • behindthescenes
  • bugs
  • c++
  • censorship
  • codechef
  • codeforces
  • contests
  • crocchamp
  • editorial
  • editorial.srm
  • embarrassing
  • explanation
  • gcj2013
  • gmp
  • goodday
  • google
  • googlecodejam
  • greed
  • groklaw
  • health
  • html
  • httpseverywhere
  • implementation
  • ipsc
  • ispc
  • java
  • kawigiedit
  • kindagoodday
  • lamebook
  • languages
  • lego
  • listedlinks
  • marathon
  • nasa
  • offtopic
  • ouch
  • postmortem
  • postportem
  • practical
  • probably_not_a_good_tip
  • problemsetting
  • programming
  • python
  • quora
  • rant
  • recap
  • slightlygoodday
  • snippet
  • srm
  • stl
  • strategy
  • swerc
  • tco
  • tco12
  • tco13
  • tco2012
  • tco2013
  • ternarysearch
  • topcoder
  • tricks
  • ubuntu
  • uva
  • vjass
  • vkcup
  • wc3
  • zinc

Blog Archive

  • ►  2014 (1)
    • ►  January (1)
  • ►  2013 (141)
    • ►  December (14)
    • ►  November (8)
    • ►  October (13)
    • ►  September (11)
    • ►  August (14)
    • ►  July (15)
    • ►  June (13)
    • ►  May (13)
    • ►  April (12)
    • ►  March (11)
    • ►  February (11)
    • ►  January (6)
  • ►  2012 (94)
    • ►  December (5)
    • ►  October (6)
    • ►  September (8)
    • ►  August (6)
    • ►  July (3)
    • ►  June (5)
    • ►  May (8)
    • ►  April (10)
    • ►  March (20)
    • ►  February (16)
    • ►  January (7)
  • ▼  2011 (51)
    • ►  December (7)
    • ►  November (12)
    • ►  October (5)
    • ►  September (1)
    • ►  August (3)
    • ►  July (4)
    • ►  June (3)
    • ▼  May (7)
      • Thoughts after SRM 507
      • Thoughts after GCJ 2011 round 1
      • 2010's gcj prediction about 2012, how is it going?
      • std::pair is good
      • Bugs that come back to haunt you.
      • Some thoughts after SRM 506.
      • Google code jam qualification round
    • ►  April (3)
    • ►  March (2)
    • ►  February (1)
    • ►  January (3)
  • ►  2010 (9)
    • ►  December (4)
    • ►  October (1)
    • ►  June (1)
    • ►  May (1)
    • ►  January (2)
  • ►  2009 (1)
    • ►  December (1)
Powered by Blogger.

About Me

Unknown
View my complete profile