Showing posts with label acm. Show all posts
Showing posts with label acm. Show all posts

Sunday, February 26, 2012

ACM 2012 World finals warmp up I

This contest was fun. So much that I stayed after the 5 official hours (Although I had long breaks). I accomplished the sorry amount of 2 correct problems during the 5 hours and later did 2 more.

It feels like the contest system at UVA is not aging graciously. The absence of AJAX can really be felt at times. It is also frequent to see low participation in these contests even though the problems are usually good and there is always something solvable.

Problem G: February 29
This is the first problem I felt like solving. It is really mostly an implementation conondrum. I would suggest ignoring the amount of days of each month for the most part and just considering whether a given date is before, after or equal to February 29. Once you have that, also calculate then umber of leap years less than a given year. Combining these things with ad hoc magic, you get a quite simple solution:

map<string,int> monthid; 

int fix(int m, int d)
{
//before, after, or equal to february 29?
if (m < 1) {
return 0;
}
if (m == 1 && d < 29) {
return 0;
}
if (m == 1) {
return 1;
}
return 2;
}

// how many leap years in years x : (0 <= x < year) ?
int before(int year)
{
// x: number of multiples of 4
int x = (year-1) / 4;
// y : number of multiples of 100
int y = (year-1) / 100;
// z : number of multiples of 400
int z = (year-1) / 400;

return x - y + z;

}

int solve(string s1, int d1, int y1, string s2, int d2, int y2)
{
int m1 = monthid[s1];
int m2 = monthid[s2];

int x1 = fix(m1,d1);
int x2 = fix(m2,d2);

if (x1 > 1) {
y1++;
}
y2 ++;
if (x2 < 1) {
y2 --;
}
if (y1 >= y2) {
return 0;
}
//cout<<"["<<x1<<" ; "<<x2<<endl;
//cout << "{"<<y2<<":"<<before(y2)<<" ;; "<<y1<<":"<<before(y1)<<endl;
return before(y2) - before(y1);

}

void init()
{
const char* s[12] = {"January", "February", "March", "April", "May",
"June", "July", "August", "September", "October", "November",
"December"};
for (int i=0; i<12; i++) {
monthid[s[i]] = i;
}
}


Problem J: Forwarding emails
It sometimes feels like the easiest problem in ACM contests is J. Why? I don't know. But it looked like a lot of people were solving this problem, so I opened it.

Every martian has exactly one martian to forward the email. Note that there are always cycles in the input. The solution is to first find all the cycles, for each martian that belongs to a cycle, save the amount of martians that are in the cycle. Once you do that, you can do the following dp-friendly recursive relation on the remaining martians. The maximum number of martians you can meet by emailing martian x is:
- Cycle size[x] if he belongs to a cycle.
- Or 1 + solution( sends[x] ) in the other case. (sends[x] is the martian x sends an email to.

Problem C and D: I plan to explain these later in big posts because they are interesting.

An issue I tend to have in contests hosted at UVA is that sometimes I have no idea what the problem expects me to do. For problem C, I spent a lot of time wondering if the intended solution is O(n*n), O(n*n*log(n)) or o(n*n). The humongous number of submissions I made to this problem was actually to reverse engineer the sizes of test cases so I can estimate whether some solutions were faster or not than others. Part of my issues here is that I am apparently unable to use hash_set and unable to implement a fast enough hash table that is actually faster than the n*n*log(n) solution I came up with first.

One hour before the end of the (official) match, I had to go for lunch. I came back two hours after it ended, but continued trying to solve C. I finally did it with a very hackish solution that is sort of n*n*log(n) but does a very dirty trick, more details later.

Problem D is cool because it exploits two cliches (Interval trees and calculating sums of arithmetic series in O(1)) into a problem that is actually interesting.

Problem E
Opened this during the match as it seemed popular. I am still not sure about what is a valid path here. Tried asking for clarifications, but I would say that no one actually reads those emails. This was the last problem I tried seriously, but I started to try to solve it around this morning and ended up without time to finish debugging.

Sunday, February 19, 2012

What is it like to be a (topcoder) problem setter?

You know, there's a question I never get asked: [What is it like to be a problem setter?] but since this blog lacks content, here is the answer anyway. Let's first answer another question: Why be a problem setter?

Money is the root of all problems
If you asked me why do I write problems, I would say that the main reason was money. Yes, really. Money. Money is my main objective of problem setting in topcoder.

No, I don't mean that it is possible to make problem setting your main source of income. Don't ever think that. Problem setting money comes too infrequently for that to be possible. I know that.

It's not the money, it is making money out of algorithms
Well, if the problem setter money is not a big deal then how could I say that money is the main reason I do it? Well, in reality, although money is the main objective of problem setting, I do not mean that money is my final objective. My final objective is to keep playing this game of algorithm contests. It is fun.

Programming contests are a hobby. If there is any serious justification for this hobby it is normally a very long term objective, such as getting good rating to impress some of the few employers that would care about it or to reach a championship after improving yourself for years. Plenty of other times there is not even one of those objectives, and I got to accept that I am one of those guys. I like participating in contests, it is fun for me. Pure algorithmic problems are so... pure, and beautiful. I get to use math rather than design databases and I hate databases, I hate soulless programming, I really do.

... The problem though is that it is very hard to explain your wonderful hobby. And it is even harder when you have a contest scheduled for a day and it overlaps with something else. It is not easy to say "Sorry, I have a contest so I cannot go". At least it was not easy for me before I started problem setting.. Thanks to problem setting money, my beloved hobby is not only a hobby but also a means to make money and I could even call it a job. It makes it a lot more justifiable to spend time working on a contest, and solving a problem. It removes a lot of guilt and it makes it acceptable for you to miss something in order to participate in a contest.

Wait... Then why did you make so many problem sets for free?
In my case, if I ever donated problems for member SRMs, it was with money in mind. Doing stuff for free is a good way to show that you can do that stuff. So that later you will be able to... say, get your problem sets accepted for the Topcoder open, when you are in a bad year and you really need to renounce about getting a TCO t-shirt and it is more convenient to write problems. (Think about it, btw, last year I made more money in the TCO than most of the onsite finalists).

Let's forget about money, What are your other objectives?
Easy, as I mentioned, I really like algorithm contests. I really , really do. There are few things more beautiful than finding out that the problem involving diamonds in a board, is actually about Eulerian paths.

How are problems born?
Thinking of problems is a lot like solving problems. Except that sometimes, you have no idea if the problems have a solution at all and other times, you have no idea if the problem exists. It normally is a very painful process that requires you to keep a notepad and make it a habit to draw weird stuff and look like a lunatic whilst everyone else is doing chit chat.

Sometimes, you don't initially know if there is a solution. Let us say you are frustrated about professors always being late, and suddenly you ask yourself "I wonder if that could be a topcoder problem". Then try to solve it. 99% of the time, there is no solution, or the solution is too easy or too difficult. Or maybe you are not Petr and can't think of the solution.

Other times, you have the solution but there is no problem. Let us say that you have failed to invent a 1000-points problem. You start trying to wonder if you could think of a problem that somehow combined algorithms you think are suitable for that problem difficulty. I noticed that this approach of thinking of the solution first and then of the problem is very good for easy problem levels - It is great for problem levels bellow div1 medium.

A cheap alternative is to take a problem you already know and make it harder somehow. Maybe that problem that had a constraint of at most 50 is actually solvable with at most 1000. The main complication is that your problems have to be original and interesting. So I usually avoid to try this unless the problem is mine.

There are occasions in which you scare yourself. The creative process is like that. Maybe you are in the bus and suddenly think of a new logical conundrum that would be a great div1 easy and then you quickly think of the back story featuring a sphinx and chickens. There is that time in which I thought of a complicated problem involving stacks and a new language and it came out of nowhere.

Once you think of the problem, you still have to get it approved. I only have experience with TopCoder, and to be honest, this is the hardest part. When I first started, I had to convince Olexiy. It was easy because my first problem set was a TCHS (high school) problem set and apparently no one cares. The it became hard because Olexiy left and mystic_tc (Ivan Metelsky) was in charge. I also wanted to write a real SRM. Later rng_58 took charge. Please note that mystic and rng_58 are the first and second place of last year's google code jam. In other words, your problem has to impress some of the best coders in the world.

More often than not, my final problem sets are drastically different from the stuff I had before I ask the admins to approve them. They usually find very easy solutions to the problems I tried to shape for months. Other times they say "That problem is too banal for div1 medium". Other times, the problem difficulty levels get swapped - The problem I thought was a division 1 easy, turns out to be a division 1 medium or even hard". There are occasions though, in which they provide ideas to make the problems more interesting. In fact, if I was completely honest, I would admit that more than one of the div1 hard problems I wrote are at least 95% their idea. They just took the premise I initially had and generated an incredible problem by changing the constraints and finding a very hard solution for them.

The development part
Even after your problem is approved, you still have to develop it. It is likely that Ivan and rng_58 received only a draft of your problem. You now have to make a good looking final version of the problem statement and to create a solution and test cases and something called checkData.

TopCoder is very professional and thus offers a tester to correct the language of your problem statement. As you can guess from my editorials and this blog, my English is so bad that they usually have to correct everything in it. Test cases are more of a joint operation. I try to make random test case generators that cover a lot of different variations and patterns, but there is also the fact that you and at least two other testers wrote a solution for the problem. One of the first solutions is almost always wrong, so finding test cases involves trying to break them. The testers are usually good at finding test cases.

In TopCoder, contestants can create their own test cases. This requires a way to verify that the test case is valid. This is the checkData method. It is a source of pain and rating cancellations, because a mistake here can cause invalid challenges during a match's challenge phase.

Either way, you will probably be receiving notifications about things to change in your problems all the time. And you have to change the problems when you have time. You can also discuss against some suggestions, because sometimes the suggestions change the problem in a way you do not like. But you have to keep objective, and it is better to, more often than not, listen to the testers.

I remember my first SRM, SRM 438. If would do that SRM again, a lot of things would be very different. My greatest mistake was not agreeing to the tester in some important matters. The match was harder than it should have been. Much harder, in fact.

And then...
Your problem set is ready, and all you have to do is wait and see what happens. You have to log in as an admin and answer questions. Then the match ends and here's the bad part: Coders from all over the world have failed the match and their easiest target is the problem set. I know this, because I've been there both as a contestant and as a problem setter. Many times, the problem set I wrote really was wrong. Other times, not so much. But there will always be someone that does not like the problem set. You have to be ready to defend your decisions and also to look at them critically and admit your mistakes. The good news is that the community itself as a whole tends to remain objective.

The worst case scenario is to mess a problem up in a high profile match. That happened to me last year in the topcoder open with RadarSabotage. It was very bad because the problem itself is... beautiful (By now you will have to admit that I am the sort of people that finds true beauty in mathy stuff). You have to learn of mistakes. IE: The mistake in RadarSabatoge was to add things that were not needed. I will never do that again. When you add a test case or a step that isn't needed or does not make sense. There is always the risk that your idea about it not being needed is actually wrong.

There are also good things that happen to you. It is a great thing when someone tells you that they liked the problem set. It is also nice when red coders ask you how to solve the problem. Or when the admin admits he liked your problems.

It also improves your skills. In order to write a problem, you really need to know the problem better than to just solve it. It may also change your perspectives about team work and let you work with some of the best coders around, did I mention those two guys who were 1st and 2nd place in the code jam last year?

Updated: I made some language corrections. I also forgot to mention that this post was inspired by Nickolas' post in codeforces. Although that is probably obvious.

Sunday, November 27, 2011

SWERC 2011, D, F, G, H and almost E

I had to wake at 5:15 AM for this, and it was still 65 minutes late. I messed up this time because I didn't know the NWERC problem set was also going to become a contest, but at spoj (which is more updated language-wise than uva) and 1 hour later (which would have been perfect for me).

At the end, I solved 4 problems at the 9-th position.

Problem D - Distributed ballot boxes
By the time I arrived, there were already stats , so I picked the problem that was solved the most in the stats. It was D.

You have to assign at least one box to each city, which means all the people will have to use that box. If the city has more than one box then you can decide who in the city gets which box, (but it is better to divide the quantities, because you want to minimize the maximum number of people to use the same box).

At first I thought the problem was just to accumulate all the populations and divide. (mind you, I was still waking up). Then I figured you cannot do that, you cannot move citizens across cities.

I thought of various things, I eventually settled for this. The trick is : What if the maximum for another city was already X? Then there is no point in using our effort trying to get a value smaller than X for another city.

That idea can be updated to a binary search. Let us change the problem into can the largest amount of citizens that use a single box in all the cities be less than or equal to ha?. If some value for ha is not possible, then smaller values won't be possible either. If a large value is possible, then any larger value will be possible too.

So, to answer the above question, we just need to find the minimum number of boxes needed by each city so that no box holds more than ha citizens. Note that if the sum of all the minimums exceeds b (The number of boxes we have to assign). Then ha is not possible.

For the minimum number of boxes needed by a city, know that all boxes should better have about the same number of citizens assigned. The best way to do this is with just a division.


//========================================================= 
// program:
//
// (initially, parse the input so that N,B and the population[] hold it).
int N, B;
int population[500000];

// is it possible to assign the boxes to the cities such that no box holds more
// than mx citizens?
bool possible(int mx)
{
int minb = 0;
for (int i=0; i<N; i++) {
//least amount of boxes we need to split population[i] in
// parts no larger than mx.
int x = population[i]/mx + (population[i]%mx != 0);

minb += x;
if (minb > B) {
return false;
}
}
return true;
}

int solve()
{
// Binary search for
// the minimum x such that possible(x).
int lo = 0; // We know possible(0) is false
int hi = 5000000; // We know possible(hi) is true (constraints).
while ( hi > lo + 1 ) {
int ha = (lo + hi)/2;
if (possible(ha)) {
hi = ha;
} else {
lo = ha;
}
}
return hi;

}



Problem H - Peer review
It seems that with ACM regionals there are always at least 3 or 4 problems that are way too straightforward and mostly annoying implementation. This is one of them.

Something I had issues with was that you can only count at most one mistake per paper. So, what exactly to do when a single scientist reviews the same paper twice? Somehow the 'per paper' part was confusing when scientists could be the ones with problems. At the end I concluded that in that case, we should count the mistake once, because it is a single paper that is being used twice by the same scientist.

A trick here is to convert the input. The problem is based on papers, yet the input is sorted by authors. It is easy to generate an array with the papers as indexes.

Then do exactly what it says on the tin. For each paper, verify that exactly K scientist review it. That nobody with the same institution as the paper's author reviews it and that each of the scientists reviewing it is distinct.
int K, N; //(From the input) 
string institution[1000]; //(From input, institution of the i-th author)
int review[1000][5]; // author x reviews paper #review[x][i] for (i < K)

int reviewed[1000][5]; // paper x is reviewed by scientist #review[x][i]
int reviewedn[1000]; // number of reviewers (shall be K at the end).

int paperHasProblem[1000]; //Allows us to count at most a problem per paper.


int countProblems()
{
fill(reviewedn, reviewedn + N, 0);
fill(paperHasProblem, paperHasProblem + N, 0);

// convert (from authors as indexes to papers as indexes).
for (int i = 0; i < N; i++) {
for (int j = 0; j < K; j++) {
int p = review[i][j];
int &t = reviewedn[p];
if (t == K) {
paperHasProblem[p] = true;
} else {
reviewed[p][ t++ ] = i;
}

}
}
// handle it
for (int i = 0; i < N; i++) {
// Exactly K reviewers, no less no more
if ( reviewedn[i] < K ) {
paperHasProblem[i] = true;
}
if (paperHasProblem[i]) {
continue;
}
for (int j = 0; j < K; j ++) {
// No collaborators
if ( institution[i] == institution[ reviewed[i][j] ] ) {
paperHasProblem[i] = 1;
break;
}
}
// no repeats
for (int j = 0; j < K; j ++) {
if (paperHasProblem[i]) {
break;
}
for (int s = j+1; s < K; s++) {
if (reviewed[i][j] == reviewed[i][s]) {
paperHasProblem[i] = 1;
break;
}
}
}

}
return accumulate(paperHasProblem, paperHasProblem + N, 0);
}



Problem F - Guess the numbers
It seems that in every ACM-related contest there is a problem that is about evaluating a parenthesis expression. And every time the complicated part is to evaluate them. But if you know the words recursion or stack it is not so hard...

So, really, there are n unknowns. And n is at most 5. You have n values available, and you have to assign them to the unknowns. You can just try all the Factorial(5) ways to do it. Then all that is needed is to evaluate the expression and see if it is equal to m.

At first I was not sure if I would have to be worried about overflows. Then I noticed that 50*50*50*50*50 is the largest factor you can have.

Note that the statement never says that the unknowns will be in sequence. So , we cannot be for sure that for n=3, the unknowns will be a,b,c. Knowing ACM problems, it is likely there were cases with n=3 and , for example, p , z, x. I do not know this for sure because I didn't try submitting without handling this corner case. But I bet that's true.

int n, m; //(From the input) 
int values[5]; // The available values, also from the input.
string expression; //The expressiong (Guess where it comes from).

// Evaluate the parenthesis expression starting at a.
// b will be the first index greater than a that is NOT part of the expression.
int evaluate(int a, int& b)
{
// The expression is an atom, just return the assigned value.
if (expression[a] != '(') {
b = a + 1;
return values[ expression[a] - '0' ];
}
// expression[a] is a (.
// then it follows another complete parenthesis expression
int p = evaluate(a+1, b);
// then an operator
char op = expression[b];
// and then another expression
int q = evaluate(b+1, b);
assert( expression[b] == ')' );
// The end of the main expression comes just after the end of the second
// sub-expression.
b++;
int r;
switch(op) {
case '+':
r = p + q;
break;
case '-':
r = p - q;
break;
case '*':
r = p * q;
break;
}
return r;


}

bool isPossible()
{
int t = 0;
// First of all, let us update the expression so that the unknowns
// are represented as fixed numbers instead of arbitrary letters.
map<char, int> assign;
for (int i=0; i<expression.size(); i++) {
char ch = expression[i];
if (ch >= 'a' && ch <= 'z') {
if (assign.count( ch )) {
expression[i] = ('0' + assign[ch] );
} else {
expression[i] = ('0' + t);
assign[ch] = t++;
}

}
}
// Try all permutations of values, evaluate and if it is m, return true.
t = expression.size();
sort(values,values+n);
do {
int t;
if ( evaluate(0, t) == (int)m ) {
return true;
}
} while (next_permutation(values, values+n) );
return false;
}


Problem G - Non-negative partial sums
Finally a more interesting problem. However, there was something I did not like about it, will explain bellow.

Ok... so the number of elements is 1000000. That's... high. You would certainly need O(n) or O(log(n)*n) solution to solve this. And that does not sound easy because you have to cycle the sequence n times and calculate the sums n times!

Due to the trauma from SRM 524 (That very special medium problem). I tried to keep things like accumulated sums in mind. Turns out they are partially helpful here.

The trick is to try your own sequence. How about: 7,-3,4,-5. Let us try the sums: (7, 4, 8, 3). These are the partial sums for the first rotation (no rotation at all). As you can see, there are no negative values here. What is interesting here, is what happens after a single rotation: (7,-3,4,-5) will become (-3,4,-5,7), but what happens to the accumulated values? They become (-3, 1, -4, 3). We now have negatives, so this rotation does not count.

We need to know how to convert the accumulated results of the first rotation into the second one. It is not actually very hard. From the first sums (7,4,8,3), we will remove the first element (a[0] = 7). If we remove the first element, the sums will become (4 - 7, 8 - 7, 3 - 7). However, we also need to add the first element, but to the last position. The new element will be (3 - 7 + 7). Because the last accumulated sum is (3 - 7) and we add 7.

Note that we do not really need the whole sequence of accumulated sums. What we do need is to know if any is less than 0. Note that if the minimum is not negative, then none of the elements in the sequence of sums will be negative. So we only need the minimum. When we remove a[i], it will make the values in the sequence of sums drop a[i] units. We will then remove a[i-1], that means to once again subtract a[i-1] from all sums. We can just keep a variable offset which will be the current number that we subtracted from all the sums.

Now, for each rotation, we just need a) The minimum value among all the current sums. b) The offset variable. If the (minimum + offset) is less than 0, then the rotation contains a negative.

We do need to update the minimum every time we remove and add elements from the sequence. And this better be fast. You can use a data structure like a tree to do this. Did you know that the STL's std::set is actually a very good tree structure (a red-black tree to be honest). It can find the minimum in the set in O(log(n)) time (using the .begin() method). It can also remove elements in O(log(n)) time, but you would need some sort to find them. And here I propose a pair, that way you store (acumulated_value[i], i) in the set. You can erase the i-th sum looking for (acumulated_value[i], i).

The first code I submitted looked like this:

int n;           //from the input 
int a[1000000]; // the values from the input
int acu[1000000]; //acu[i] will hold sum(a[j], for j<=i).

int solve()
{
// Build the first sequence of sums, add to our "tree"
set< pair<int,int> > tree;
int res = 0;
acu[0] = a[i];
tree.insert( acu[0], i);
int last = acu[0];
for (int i=1; i<n; i++) {
acu[i] = acu[i-1] + a[i];
tree.insert( make_pair(acu[i],i) );
last = acu[i];
}
res = ( tree.begin()->first >= 0); //if we found a negative acu, first cycle is wrong.

int offset = 0;
//The remaining rotations
for (int i=1; i<n; i++) {
// We remove a[i-1]
tree.erase( tree.find( make_pair(acu[i-1], i-1) ) );
offset -= a[i-1];

// We add last + a[i-1]
int id = i + n;
tree.insert( make_pair(last + a[i-1], id) );
// We update last:
last += a[i-1];

//verify the minimum
if (b->first + offset >= 0) {
res ++;
}

}
return res;
}


Unfortunately, it timed out. Which is very lame because it is clearly O(n*log(n)). It seems that as usual uva wants you to care about the constant factor. Which is something I don't like much (Because it teaches us the terrible lesson of not using libraries, when instead using libraries (a good practice) should be rewarded and not the other way around). I eventually came up with a solution that uses the same logic, but not a tree, instead we just sort the first sequence of sums. It has the same complexity but less overhead related to set and it passes in time.

int n;           //from the input 
int a[1000000]; // the values from the input

pair<int,int> acumvec[1000000];

int solve()
{
int res = 0;
// Build the first sequence of sums, then sort the vector
int acu = 0;
int last = 0;
for (int i=0; i<n; i++) {
acu += a[i];
acumvec[i] = make_pair(acu,i);
last = acu;
}
sort(acumvec, acumvec+n);
res = ( acumvec[0].first >= 0 ); //if we found a negative acu, first cycle is wrong.

int t = 0;

int offset = 0;
//remaining cycles
int ultramin = 2000000000; //The idea is that we will never have to remove
//the indexes greater than (n-1), so we do not need
// them in a tree or anything.
// This also means that we do not really need to
// update the first vector, so the order will
// always be the same and thus we only need to sort once.
for (int i=1; i<n; i++) {

int id = i + n;
// We 'remove' i-1
while( (t < n) && ( acumvec[t].second < i) ) {
t++; //Note that we will visit each t at most once IN TOTAL, so it is O(n)
// but outside the for(i=1;i<n;i++) loop.
}

offset -= a[i-1];
// We "add" last + a[i-1]
ultramin = min(ultramin, last + a[i-1]);
// We update last:
last += a[i-1];

// If the minimum + offset < 0, there are negative sums.
// we need to consider both minimums...
if ( (ultramin+offset < 0) || ( (t < n) && (acumvec[t].first+offset < 0) ) ) {

} else {
res++;
}
}
return res;


}



Problem E - Game, set and match
I tried many other problems but it was already quite late and didn't have promising ideas. Around 20 minutes before the end, I opened E. I think this problem is really not as hard as the statistics would tell you. But it does need you to implement 4 different subproblems and that takes quite some time.

My initial idea is dynamic programming with some handling of special cases. I only solved the game part so far. Here is an explanation about how to solve games.

Let us define f(a,b), the probability that the player will win a game if his score is a, and the other player's score is b. Note that the scores defined in the statement are 0,15,30,40, but we can consider them as (0,1,2,3). We will add a new score 4, that means a player won. The player that has score a has a p probability to win a point and we want to calculate his probability to win.

Let us find f(a,b) when they are both are 3, this is the most interesting case (equivalent to 40, 40 in the statement) as they are in a "deuce". We are interested in seeing the player win 2 consecutive times. But if the players score two alternate points, we are back to 40,40. That is important. Let us put things into an equation:

f(3,3) = p*p + p*(1-p)*f(3,3) + (1-p)*p*f(3,3) + (1-p)*(1-p)*0.

- If the player scores two consecutive points, that has probability p*p, and he wins. If the other player scores two consecutive points, that has probability (1-p)*(1-p) and the first player loses (thus the 0). Else, in the other two cases each of the players scores once in the two next turns. This takes us back to the starting point.

The equation is just that, an equation. You can solve it and you have f(3,3).

Now, once you have f(3,3), you can solve the remaining cases of f(a,b) . When a is 4, the player won, that is probability 1 to win. When b is 4, the other player won, so the first player has 0 probability to win. Else there is p probability that the game will become f(a+1,b) and (1- p) probability that it will become f(a,b+1). We just need some good-old memoization to solve the f recurrence.

The other parts of the statement are solved using similar methods, use memoization and solve equations to solve cycles. But it should take long to deduct everything and code.
// Does not solve all the problem, only finds the probability to win a game. 
bool rgame[5][5];
double game[5][5];

double gameProbability(double p, int a=0, int b=0)
{
double & res = game[a][b];
if (!rgame[a][b]) {
rgame[a][b] = true;
if (a==4) {
res = 1;
} else if (b==4) {
res = 0;
} else if ( (a==3) && (b==3) ) {
//how to win a deuce
// f = p*p + (1-p)*p*f + p(1-p)*f
res = (p*p)/(1 - 2*p + 2*p*p);
} else {
res = p * gameProbability(p, a+1, b)
+ (1-p) * gameProbability(p, a, b+1);
}
}
return res;
}

//=========================================================
// I/O:
//
int main()
{
double p;
while ( (cin >> p) && (p > -0.1) ) {
memset(rgame,0,sizeof(rgame));
memset(rset,0,sizeof(rset));
cout.precision(11);
cout.setf(ios::fixed, ios::fixed);
cout << gameProbability(p) <<endl;
}
return 0;
}