Pages

Wednesday, December 16, 2009

Discrete Processes: Binomial Tree Model

After reading a bit from Baxter and Rennie's book about discrete processes, I decided to implement the basic binomial tree described in the book. It's a build onto the binomial branch model, in essence it's the same thing but done on a recursive scale. It is mostly used for evaluating the cost of an option via arbitrage. The main downfall to discrete process modelling computationally is that it becomes exceedingly difficult to scale it for problems of large instances. Unlike continuous processes which are evaluated through approximation, discrete processes models each tiny step and for an accurate representation would require a lot of memory and computational time.

The model suggests having a portfolio of stocks and bonds. This simple combination allows us to synthesize the derivative by replicating desired values. The key point is the probability of up and down moves from a given stock at a given time does not influence our ability to generate the desired values. For example, we can say for certainty that if the stock moved up then down and then up again we would reach our desired value f(n). We use an algebraic relation from the next stock up and down prices to determine our claim value. We evaluate the claim value from the back of the tree where the payoff/desired value is guaranteed and move our way back to our initial node. This claim value at the initial node is what we should price the option at.

Now if the market was to move up or down, we need to adjust our proportion of stocks and bonds to compensate. It's essentially like a board game, when the stock moves we make an appropriate adjustment to ensure that the next move (either up or down) is within our described tree regardless of the outcome. At the end of the time period, we are guaranteed to get the claim value for the given sequence of stock movements from t = 0 to t = n, where n is the number of time ticks (i.e. depth of the binomial tree). For more information refer to Baxter and Rennie's Financial Calculus book.

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <algorithm>
#include <cmath>

using namespace std;

// represent the stock as a value in time
class Stock {
public:
   double price;
   double prUp;
   double prDown;
   Stock() { }
   Stock(double p, double up, double down) : price(p), prUp(up), prDown(down) { }
};

vector<double> fTable;   // represents the claim tree
vector<Stock> graph;   // represents the recombinant tree
int sz;

#define EPS 1e-09

// claims are calculated as:
//      q = (stock_now - stock_down) / (stock_up - stock_down)
//      f = q * f_up + (1 - q) * f_down
// base conditions can be computed based on valuation of the option at t=0 versus the
// payoff given a sequence of situations (when the stock goes up/down for t=0 to t=n).
double computeClaims(int node) {
   int baseNode = (sz-1) >> 1;
   if (fTable[node] >= 0) {
      // memoisation
      return fTable[node];
   }
   if (node >= baseNode) {
      // base case
      return fTable[node] = max(0.0, graph[node].price - graph[1].price);
   }
   // compute
   double q = (graph[node].price - graph[node*2+1].price) / 
      (graph[node*2].price - graph[node*2+1].price);
   fTable[node] = q * computeClaims(node*2) + (1 - q) * computeClaims(node*2+1);
   return fTable[node];
}

// define an recombinant tree and reconstruct the option value
int main() 
{
   int N;   // the depth of the tree
   cin >> N;
   graph = vector<Stock>((1 << N) + 1);
   
   // read in the recombinant tree (stated in order of time)
   // file format:
   // stockprice prUp
   // stockprice2 prUp2
   // etc.

   int totalCnt = (1 << N);
   for (int i = 0; i < totalCnt-1; i++) {
      double stockPrice, prUp, prDown;
      cin >> stockPrice >> prUp;
      prDown = 1 - prUp;
      graph[i+1] = Stock(stockPrice, prUp, prDown);
   }

   // reconstruct the option price by backtracking
   fTable = vector<double>((1 << N) + 1, -1.0);
   sz = (1 << N) + 1;
   double optionPrice = computeClaims(1);
   cout << "Option price is " << optionPrice << "\n";
   
   // run simulations (optional)
   // file format:
   //   sequence of {up, down}'s equal to N-1.
   // note: stockHolding is calculated by:
   //   s = (f_up - f_down) / (s_up - s_down)
   string status;
   string lastStatus = "-";
   int cnt = 0;
   int curTime = 0;
   double stockHolding = 0.0;
   double bondHolding = 0.0;
   int curBranch = 1;
   while (cin >> status && cnt++ <= N-1) {
      // output the status
      cout << "Time: " << curTime << " Last Jump: " << lastStatus << 
         " Stock Price: " << graph[curBranch].price << 
         " Option Value: " << fTable[curBranch] << " Stock Holding: " 
         << stockHolding << " Bond Holding: " << bondHolding << "\n";
      curTime++;
      lastStatus = status;
      stockHolding = (fTable[curBranch*2] - fTable[curBranch*2+1]) / 
         (graph[curBranch*2].price - graph[curBranch*2+1].price);
      bondHolding = fTable[curBranch] - (stockHolding * graph[curBranch].price);
      if (status == "up") {
         curBranch = (curBranch * 2);
      } else {
         curBranch = (curBranch * 2) + 1;
      }
   }
   if (cnt > 0) {
      cout << "Time: " << curTime << " Last Jump: " << lastStatus << 
         " Stock Price: " << graph[curBranch].price << 
         " Option Value: " << fTable[curBranch] << " Stock Holding: " 
         << stockHolding << " Bond Holding: " << bondHolding << "\n";
   }
   return 0;
}

With an example file input based on the book examples (pipe it in):

4
100 0.75
120 0.75
80 0.25
140 0.75
100 0.25
100 0.75
60 0.25
160 0.75
120 0.25
120 0.75
80 0.25
120 0.75
80 0.25
80 0.75
40 0.25
up
up
down

Tuesday, December 8, 2009

TC SRM 454 D1-500/D2-1000 (NumbersAndMatches)

Category: Dynamic Programming
URL: http://www.topcoder.com/stat?c=problem_statement&pm=10709

We are given a number which is composed of a sequence of digits. Each digit is represented by a combination of matches represented in a certain position to represent the digit. We are given the option of using up to K different match moves and are asked to calculate the number of different integers we can construct without discarding or adding any new matches to our given number.

First, we decompose the problem into several parts. The first involves somehow representing each digit in our program. A simple way to do this is to use a 2D integer array in which index (i,j) is equal to 1 if the digit i has a match in position j. An alternative way is to use a bitmask with the bit representation of the j-th bit set to 1 if there is a match in position j.

Next, we need to somehow compute the transitional costs between two different digits. For example, we need to quantify how many moves we would use up if we convert say a 6 to a 3. Since we are only allowed a limited number of moves its obvious we need a way to keep track of how many we have used up. One possible way is to determine how many places the two numbers differ. Then the number of moves we need to make is equivalent to exactly half of this.

There is another issue we need to deal with, that is the situation where the number of matches used to construct two digits differ. For example, the digit 6 uses 6 matches whereas digit 3 only uses 5 matches. We need to keep track of how much surplus (or deficit) matches we get from transitioning from one digit to another. This is important as we cannot add or remove matches from the final number - i.e. the number of matches used in constructing a unique integer must be the same as the original integer. Now we need to keep track of two things when we consider transforming one digit to another: the number of moves it takes and the difference in matches we have used so far.

We can define the cost function as follows:



To calculate the surplus/deficit it's quite simple, just calculate the number of matches used for each digit and subtract one from the other depending on which direction the transformation is taken. The above cost function ensures that we don't double count the number of moves required from moving a match from a surplus digit to a deficit digit. A way to visualise this is we "precharge" the surplus with an additional move but we are also granted a free move since this surplus is already counted once so the actual number of moves decreases if we need to import more matches from another transformation.

Lastly, we need to somehow compute all the ways we can construct unique integers given that we keep the number of matches used the same (i.e. at the end of our decision algorithm the number of surplus matches is exactly 0) as well as using less than or equal to K moves (which we subtract along the way). This suggests a recursive algorithm from potentially changing the first digit to something else, depending on the transformation we are left with either more/less/equal number of matches and the number of available moves to be less or equal to what we started with. We must also handle the case where we "borrow" matches from the latter part of the integer, so we must also handle cases where our match balance is negative. This can be done by computing an offset to keep the total number positive (to use for our caching purposes).

To efficiently compute the number of ways we need to memoise or apply bottom up DP to our algorithm. Note that the number of unique states is rather small so computational time isn't an issue. An implementation of the above ideas is shown below (using memoisation):

class NumbersAndMatches {
public:
   long long differentNumbers(long long, int);
};

int mat[10][7] = {
  {1,1,1,0,1,1,1}, // 0
  {0,0,1,0,0,1,1}, // 1
  {1,0,1,1,1,0,1}, // 2
  {1,0,1,1,0,1,1}, // 3
  {0,1,1,1,0,1,0}, // 4
  {1,1,0,1,0,1,1}, // 5
  {1,1,0,1,1,1,1}, // 6
  {1,0,1,0,0,1,0}, // 7
  {1,1,1,1,1,1,1}, // 8
  {1,1,1,1,0,1,1}  // 9
};

// defines the total number of matches used for digit i
int tot[10] = {6, 3, 5, 5, 4, 5, 6, 3, 7, 6};   

long long dp[19][155][155];
pair<int,int> costTab[10][10];
string num;

#define SURPLUS_MOD 72

long long func(int idx, int moves, int surplus) {
   // offset due to the possibility of negative surplus
   int surplusMod = surplus + SURPLUS_MOD;
   if (idx >= num.size()) {
      // requires the surplus amount of matches to be 0 otherwise its not valid
      if (moves >= 0 && surplus == 0) return 1;
      return 0;   
   }
   if (dp[idx][moves][surplusMod] != -1) return dp[idx][moves][surplusMod];
   long long res = 0;
   // try each digit
   int curDig = num[idx] - '0';
   for (int dig = 0; dig <= 9; dig++) {
      int c = costTab[curDig][dig].first;
      int d = costTab[curDig][dig].second;
      if (moves - c >= 0) {
         res += func(idx+1, moves - c, surplus + d);
      }
   }
   return dp[idx][moves][surplusMod] = res;
}

long long NumbersAndMatches::differentNumbers(long long N, int K) {
   long long res = 0;
   memset(dp,-1,sizeof(dp));
   // compute the cost function for every digit -> digit transition
   for (int i = 0; i <= 9; i++) {
      for (int j = 0; j <= 9; j++) {
         int c = 0;
         for (int k = 0; k < 7; k++) {
            if (mat[i][k] != mat[j][k]) c++;
         }
         int s = tot[i] - tot[j];
         costTab[i][j] = make_pair((c+s)/2, s);
      }
   }
   // decompose the number into a string
   long long vN = N;
   while (vN > 0) {
      num += ((vN % 10) + '0');
      vN /= 10;
   }
   reverse(num.begin(),num.end());
   // calculate the number of ways
   return res = func(0, K, 0);
}

Friday, December 4, 2009

Matrix Exponentiation

Matrix Exponentiation is a useful technique that can be applied to a wide variety of problems. Most commonly it is used for efficiently solving linear recurrence problems and as such can be used in any problem that can be represented as a linear recurrence. The main problem lies with the fact that it is inefficient when we naively evaluate them. Let's consider an easy example that you should know about: Fibonacci numbers.

The sequence is defined by:



It's easy enough to increasingly evaluate it 1 by 1 each time until we hit our targetted n-th Fibonacci number. However, let's say n was 1 billion - we need at least 1 billion computations of the sequence to derive the answer. If we represent the sequence in terms of a matrix we discover a clever optimisation which can be applied.



The correctness can be verified by a simple matrix multiplication. What's more important to note is that the next two sequence of Fibonacci numbers can be represented like the one above.


If we expand:

This is equivalent to:


We can continue expanding and eventually we see a remarkable observation. We can always reduce it to the power of our matrix multiplied by our initial conditions. This yields the recurrence below:

 

Since exponentiation can be done in logarithmic time we have essentially reduced our Fibonacci computation speed from linear to logarithmic. A huge difference in speed for large numbers! As an exercise, write a logarithmic Fibonacci term generator - as the numbers do get quite large, feel free to modulo it with an appropriate number (like 10^8).

We now expand our knowledge to a slightly more complicated linear recurrence. This is based on the problem "Number Sequences": http://acm.tju.edu.cn/toj/showp2169.html

To solve this recurrence we simply just do the same thing as we did with Fibonacci with a slight modification. Fibonacci is really a special case of the above recurrence where x = 1 and y = 1 and a0 = a1 = 1. Note the prime observation below:



If we matrix multiply - we yield the correct recurrence for both Fn and Fn-1. Therefore we simply need to change our original Fibonacci matrix of [ 1 1, 1 0 ] to [ x y, 1 0] and the initial conditions from being always 1 and 1 (F1 and F0 respectively) to [a1 a0]. We then simply use matrix exponentiation to calculate the correct term, as always we apply modulo arithmetic to keep the number representable with integers.

The implementation below demonstrates this idea:
class Matrix {
public:
   long long a;
   long long b;
   long long c;
   long long d;
   Matrix() { }
   Matrix(long long _a, long long _b, long long _c, long long _d) : 
      a(_a), b(_b), c(_c), d(_d) { }
};

Matrix multiply(const Matrix& lhs, const Matrix& rhs) {
   long long a = lhs.a * rhs.a + lhs.b * rhs.c;
   long long b = lhs.a * rhs.b + lhs.b * rhs.d;
   long long c = lhs.c * rhs.a + lhs.d * rhs.c;
   long long d = lhs.c * rhs.b + lhs.d * rhs.d;
   return Matrix(a % 100, b % 100, c % 100, d % 100);
}

ostream& operator<<(ostream& out, const Matrix& M) {
   out << "a: " << M.a << " b: " << M.b << " c: " << M.c << " d: " << M.d;
   return out;
}

Matrix power(Matrix M, int n) {
   if (n == 1) return M;
   Matrix tmp = power(M, n/2);
   if (n % 2 != 0) {
      Matrix n = multiply(tmp,tmp);
      Matrix m = multiply(n, M);
      return m;
   }
   Matrix v = multiply(tmp,tmp);
   return v;
}

void output(int n) {
   if (n < 10) cout << "0";
   cout << n << "\n";
}

int main() {
   long long x, y, a0, a1, n;
   while (cin >> x >> y) {
      if (x == 0 && y == 0) break;
      cin >> a0 >> a1 >> n;
      if (n == 0) { output(a0%100); continue; }
      else if (n == 1) { output(a1%100); continue; }
      Matrix m = power(Matrix(x,y,1,0), n-1);
      output((m.a * a1 + m.b * a0) % 100);
   }
   return 0;
}

Now for a more advanced example, we use matrix exponentiation to determine the number of cycles with a length smaller than k in a given directed graph.

Problem: TourCounting
Source: TopCoder SRM 306 D1-1050
URL: http://www.topcoder.com/stat?c=problem_statement&pm=6386

An elegant way to calculate the number of paths from a source vertex u to a destination vertex v that takes exactly k steps is to use matrix multiplication. First let's define A to be the adjacency matrix with each entry representing the number of edges from u to v. The (u,v) entry of A^t is precisely the answer we are looking for. The way this works is similar to the way that Floyd-Warshall algorithm works. The matrix multiplication considers the connectivity between an intermediate node k and attempts to link u to k and k to v. Due to the multiplicative nature of matrix multiplication as opposed to an additive (for shortest paths) it determines the number of paths based on the multiplication principle from discrete mathematics.

To illustrate this principle, consider a snapshot of the matrix A^t and it's adjacency matrix which is represented as A^1:


If we were to calculate the number of paths that start from vertex 0 to itself (i.e. a cycle) that takes exactly t+1 steps. Note that we can access vertex 0 from either vertex 1 or vertex 2. Since at state t, we can reach vertex 1 from vertex 0 with 2 paths and we can reach vertex 2 from vertex 0 with 3 paths. It stands that we can now reach vertex 0 to itself from 2+3=5 paths. Note that this is a direct consequence of the matrix multiplication process - validate this yourself for the first row and column. If the adjacency matrix was changed such that there are two edges from vertex 1 to vertex 0 like the following:



Then we can reach vertex 0 in a total of 2*2 + 1*3 = 7 ways since we have an option of choosing one of the two edges from vertex 1 to vertex 0. Again, this is consequence of the matrix multiplication algorithm. The argument holds for every other cell in the matrix. So what's the advantage of this method? The prime factor is efficiency - we can calculate matrix powers in logarithmic number of matrix multiplications. We accomplish this through a similar algorithm to fast powering.



So now we know how to calculate the number of paths from a source vertex u to a destination vertex v that takes exactly k steps. The actual problem requires us to compute the number of cycles that are present with less than k steps. So for a given instance of the matrix A^t we want to sum the diagonal to yield the number of paths with exactly t steps. To calculate it for less than k steps we would normally have to compute each power of t and sum all the diagonals up, however this is not fast enough as the number of steps can be as much as 1 million. We can reduce this using a similar method to our matrix powering algorithm. First define a function f:

f(n) = represents the number of ways we can get from all vertices to other vertices using less than n steps (matrix form)

The main optimisation is reducing it from linear (summing all matrices less than n) to logarithmic. The huge hint in logarithmic is dividing the input space by a (usually) constant factor. Note that if we divide the space into two f(n/2) parts we are close to our answer but not quite there. The problem is that upper n/2 part is not symmetrical to the lower n/2 part. But wait! Given the simple mathematical property of powers a^(b+n) = a^b * a^n we can simply just multiply one of the f(n/2) parts by A^(n/2) to yield the correct upper half! With this our recurrence becomes:



Like our fast powering algorithm we need to distinguish between odd and even states. The easy way to make an odd number even is to subtract it by 1. We then just calculate one instance of the odd power and make all successively calls even. This maintains the correctness of the recurrence whilst also maintaining the time complexity.



Then it's simply a matter of implementation which is the easy part. Just note we need to use sensible modulo arithmetic to ensure that we don't overflow any of our computations.

class TourCounting {
public:
   int countTours(vector <string>, int, int);
};

class Matrix {
public:
   vector<vector<long long> > data;
   Matrix() { }
   Matrix(int n, int m) {
      data = vector<vector<long long> >(n, vector<long long>(m, 0));
   }
};

long long MOD;

Matrix operator*(const Matrix& lhs, const Matrix& rhs) {
   Matrix res(lhs.data.size(), rhs.data[0].size());
   for (int i = 0; i < lhs.data.size(); i++) {
      for (int j = 0; j < lhs.data[i].size(); j++) {
         for (int k = 0; k < rhs.data[0].size(); k++) {
            res.data[i][k] = (res.data[i][k] + lhs.data[i][j] * rhs.data[j][k])%MOD;
         }
      }
   }
   return res;
}

Matrix operator+(const Matrix& lhs, const Matrix& rhs) {
   Matrix res(lhs.data.size(),lhs.data[0].size());
   for (int i = 0; i < lhs.data.size(); i++) {
      for (int j = 0; j < lhs.data[0].size(); j++) {
         res.data[i][j] = (lhs.data[i][j] + rhs.data[i][j])%MOD;
      }
   }
   return res;
}

Matrix power(const Matrix& lhs, int P) {
   if (P == 1) return lhs;
   Matrix tmp = power(lhs, P/2);
   if (P % 2 != 0) {
      Matrix n = tmp * tmp;
      Matrix m = n * lhs;
      return m;
   }
   Matrix v = tmp * tmp;
   return v;
}

long long compute(const Matrix& m) {
   long long res = 0;
   for (int i = 0; i < m.data.size(); i++) res = (res + m.data[i][i]) % MOD;
   return res % MOD;
}

Matrix dp[1000001];
int visited[1000001];

Matrix f(const Matrix& ref, int n) {
   if (visited[n]) return dp[n];
   visited[n] = 1;
   if (n == 1) return dp[n] = ref;
   if (n % 2 != 0) {
      // odd
      Matrix v = f(ref, n-1) + power(ref, n);
      return dp[n] = v;
   }
   // even 
   Matrix vE = f(ref, n/2) * power(ref, n/2);
   Matrix vEE = vE + f(ref, n/2);
   return dp[n] = vEE;
}

int TourCounting::countTours(vector <string> g, int k, int m) {
   Matrix mat((int)g.size(),(int)g.size());
   for (int i = 0; i < g.size(); i++)
      for (int j = 0; j < g[i].size(); j++)
         mat.data[i][j] = g[i][j] == 'Y' ? 1 : 0;
   MOD = m;
   long long res = 0;
   Matrix rr = f(mat, k-1);
   res = compute(rr);
   return res;
}

Thursday, November 26, 2009

TC SRM453.5 D1 450 (PlanarGraphShop)

Category: Dynamic Programming, Graph Theory
URL: http://www.topcoder.com/stat?c=problem_statement&pm=10412

This problem asks us to determine the minimum number of simple planar graphs we need to exact use of N gold coins. Each planar graph has an associated cost function based on the number of vertices and edges the graph has, more specifically: cost = (number of vertices)^3 + (number of edges)^2. We should immediately see a knapsack-like algorithm which strongly suggests using dynamic programming. The main problem lies with determining what number of vertices and edges constitutes a valid simple planar graph.

Luckily there is a simple theorem which we can use as an upper bound calculation to determine whether a pairing of (#V,#E) is a valid pair. For graphs that has at least 3 vertices: if e > 3v - 6 then this will imply nonplanarity. Hence, we can deduce that all e <= 3v - 6 has a simple planar graph representation. For graphs that has less than 3 vertices we can simply hand calculate the combinations. Once we know what our valid pairings are we can construct a set of feasible cost moves based on our cost function by iterating through all possible pairings of # vertices and # edges. We then apply a simple DP algorithm to calculate the minimum number of planar graphs needed.

An implementation is shown below:

class PlanarGraphShop {
public:
   int bestCount(int);
};

int dp[50001];
vector<int> moves;

#define INF 1 << 20

int func(int N) {
   if (N == 0) return 0;
   if (N < 0) return INF;
   if (dp[N] != -1) return dp[N];
   int res = INF;
   for (int i = 0; i < moves.size() && moves[i] <= N; i++) 
      res <?= 1 + func(N - moves[i]);
   return dp[N] = res;
}

int PlanarGraphShop::bestCount(int N) {
   set<int> costs;
   costs.insert(1); costs.insert(8); costs.insert(9);
   for (int v = 1; v < 37; v++) 
      for (int e = 0; e <= 3 * v - 6; e++)
         costs.insert(v*v*v + e*e);
   moves = vector<int>(costs.begin(),costs.end());
   memset(dp,-1,sizeof(dp));
   return func(N);
}

Sunday, September 27, 2009

Google Code Jam Round 2

Links:
Google Code Jam Statistics: http://www.go-hero.net/jam/
Google Code Jam Scoreboard: http://code.google.com/codejam/contest/scoreboard?c=204113

Overview
Round 2 proved to be a bit trickier than Round 1, especially in terms of advancement as only the top 500 go through. The time setting didn't help either for many contestants. Unfortunately I didn't make it through but congratulations to everyone who did! It has been a major blast of fun :)

So what happened? There are several key points I've learnt from participating this round.

1. Don't focus too much on constraints!

For problem A, my first instinct was a greedy based algorithm which was rather close to the correct algorithm. However, I quickly convinced myself (wrongly) that it would fail for cases in which you would possibly make a move which turns the remaining subproblem infeasible. This does not happen though as the strictly increasing ordering of the final output prevents this from happening. This was further misled by looking at the large constraint which had a maximum of 40. Surely a greedy algorithm can't work right? Wrong! :)

Although it is always good to look at constraints (and perhaps I've relied too heavily on it) to dictate what algorithm the problem setter is expecting don't let it "blind" (i.e. let it confirm your incorrect doubts of an algorithm without fully going through it) you though as it did to me.

2. Don't get too focused on a particular methodology and learn to back out of an idea if you can't get anywhere with it!

After solving A-small and D-small, the only way I would advance is to solve both C-small and C-large (or B but fewer people tried to solve it so I also tried C). Instead of wasting time on finding the correct solution to A-large, I proceeded with problem C.

First thought was bipartite matching (and that hunch was correct) as it felt natural (because I've actually solved a very similar problem). I couldn't reduce the graph properly with the correct edge labelling criteria so I completely abandoned this idea (bad move as I did in fact at one point have the correct algorithm but made a false assumption which would of made it incorrect). I eventually deduced it to finding the minimum number of complete induced subgraphs (clique problem). However this is NP-complete, so I really should of backed out of this reduction but I was persistent as this was the only "correct" (although it would time out) solution I had. Sometimes it pays off to let go of an idea if you can't get anywhere with it which is easier said than done!

Despite being slightly disappointed I had a fun time. Unfortunately no Australians (unofficially at this point) have made it through to Round 3. Good luck for all those participating in Round 3 though :)

Official Contest Analysis is available: http://code.google.com/codejam/contest/dashboard?c=204113#s=a

Wednesday, September 23, 2009

Eulerian Circuits and Paths

Problem:
Given an undirected graph G = (V,E) we need to find a path which uses every edge exactly once - we are allowed to visit vertices multiple times to achieve our goal. If the start and ending vertex must be the same, then this is known as an Eulerian Circuit. Otherwise, this is known as an Eulerian path.

Algorithm:
There is a simple existence theorem which we can use to determine whether or not a graph has an Eulerian circuit and/or path. These are given without proofs:

- A graph has an Eulerian circuit if and only if it is both connected (i.e. from a given vertex we can reach all other vertices through some arbitrary path) and every node has an even degree (which is the number of edges a particular vertex has).

- A graph has an Eulerian path if and only if it is both connected and every node except for two has an even degree.

It follows that an Eulerian circuit is a special case of an Eulerian path in which the start and end vertices are the same. We are allowed to have two non-even degree vertices for an Eulerian path as these denote the start and end vertices.

The classic algorithm to solve this problem is called Fleury's Algorithm. Fleury's Algorithm is pretty simple and intuitive:

- Pick any starting vertex.
- Traverse in a DFS-like manner on edges that haven't been traversed yet. The special case is not to traverse a bridge unless that is the only edge remaining (this is because if we traverse through a bridge we can't come back to the other side by definition as we are only allowed to traverse an edge exactly once).
- When the edge is traversed, erase/delete the edge from the graph.
- Recurse on the new vertex and keep repeating until all edges have been traversed.

The problem with Fleury's when translating it to code is detecting bridges. We present another algorithm which is just as simple but resolves this issue implicitly through the use of backtracking. This algorithm is from the USACO training pages which can also be accessed here: http://acm.fzu.edu.cn/reference/Eulerian%20Tour.htm

Basically, we recurse like in Fleury's Algorithm without consideration of bridges. We also construct the Eulerian path/circuit in reverse order by appending book-keeping information post-processing order. For more details about this algorithm - see the above link. You may ask, how does this avoid having to keep track of bridges?

Firstly, let's make some observations. Take a look at the graph below:



Imagine we are at vertex C. We have several possible choices to traverse - either to vertex E (using edge CE), vertex F (using CF) and vertex D (using CD). Note that if we delete the edge we came from, we are essentially creating a subproblem in which we lose one edge and start "again" from the new vertex we just traversed to in the previous step. For example, if we traverse to vertex E, we are left with only two vertices E-B as a subproblem. The interesting thing to note is that the subproblems carry the same properties as our original problem. Namely, all vertices are either even or they are all even except for two.

Now we need to convince ourselves that the order of traversals of our algorithm does not matter as it produces the correct path for all cases. The first case is when our problem (or subproblem) has no bridges, in which case the order does not matter as we will not exclude any vertices/edges being visited later. The second case is when our problem (or subproblem) has bridges. Our graph above has such a bridge (edge CE). If we were to traverse through this edge before visiting the edges CD, CF and DF then we won't have a valid path as we can't go back on edge CE to visit these edges - thus by definition it won't be an Eulerian path.

However our algorithm adds vertices in a backwards manner. If we visit edge CE first, our stack for the subproblem would be: B-E. Then we visit either edge CF or CD (from backtracking to our vertex C call). The second subproblem's stack are both C-F-D (trace it yourself for edges CF and CD). Since we visited edge CE first, our problem's stack becomes B-E-C-F-D. Since we have now deleted edges CD, CE and CF. Vertex C has no neighbours and hence gets added on as well, yielding: B-E-C-F-D-C. Reversing this path we get the Eulerian path of C-D-F-C-E-B.

If we don't visit edge CE first, then we don't get more than 1 new subproblem (i.e. more than one component) as we don't "violate" the bridge (i.e. we visit all the edges on the left side before traversing it). Hence in this case, our algorithm produces the correct answer also. Intutitively this means that if we generate a disconnected component (by using a bridge too early) we actually put these towards the end of the path first and hence as a result their visiting order gets "reversed" implicitly. Therefore, we still generate a valid path and most importantly still visit all the edges, therefore we have an Eulerian path.

So what happens when there are multiple bridges? Luckily our constraint of at most two vertices having odd degrees saves us a lot of trouble. Draw it out yourself by constructing potential counterexamples!

Our particular graph only has an Eulerian path (not a circuit) because there are two edges with odd degrees (namely vertex C and B). However it is trivial to use the same algorithm for a graph with only even degree vertices - just start at any vertex! For graphs such as the one above, we must start our algorithm on one of the odd degree vertices.

Application (USACO Training Pages - Riding the Fences):

Farmer John owns a large number of fences, which he must periodically check for integrity. Farmer John keeps track of his fences by maintaining a list of their intersection points, along with the fences which end at each point. Each fence has two end points, each at an intersection point, although the intersection point may be the end point of only a single fence. Of course, more than two fences might share an endpoint.

Given the fence layout, calculate if there is a way for Farmer John to ride his horse to all of his fences without riding along a fence more than once. Farmer John can start and end anywhere, but cannot cut across his fields (the only way he can travel between intersection points is along a fence). If there is a way, find one way.

This is a simple application of the Eulerian algorithm outlined above:

using namespace std;

int adjmat[511][511];
int visited[511];
int circuit[1111];
int numEdges[511];
int circuitpos;

void euler_circuit(int pos) {
   bool change = true;
   while (change) {
      change = false;
      for (int i = 0; i < 511; i++) {
         if (adjmat[pos][i] > 0) {
            change = true;
            adjmat[pos][i]--;
            adjmat[i][pos]--;
            euler_circuit(i);
         }
      }
   }
   circuit[circuitpos++] = pos;
}

int main() {
   ifstream inFile("fence.in");
   ofstream outFile("fence.out");
   int F; inFile >> F;
   int x, y;
   int startNode = 1 << 20;
   for (int i = 0; i < F; i++) {
      inFile >> x >> y;
      adjmat[x][y]++;
      adjmat[y][x]++;
      numEdges[x]++; numEdges[y]++;
      startNode = min(startNode, min(x, y));
   }
   for (int i = 505; i >= 0; i--) if (numEdges[i] % 2 != 0) { startNode = i; }
   euler_circuit(startNode);
   for (int i = circuitpos-1; i >= 0; i--) outFile << circuit[i] << "\n";
   return 0;
}

Tuesday, September 22, 2009

TC SRM424 D1 600 (StrengthOrIntellect)

Category: Dynamic Programming
URL: http://www.topcoder.com/stat?c=problem_statement&pm=10174

This problem asks us to determine the maximum number of missions we can complete given two character attributes: strength and intellect. We meet the pre-requirements to complete a mission if either one of the strength or intellect attributes are at least as high/equal to the mission's strength and intellect requirements respectively.

A greedy approach does not work here. You can informally argue by noting that when we are given two choices to increase strength or intellect, there is no clear decision on which option to choose. The local optimality does not hold for the global optimality, as if we choose one or the other we might miss out on earning a lot of points which will allow us to complete more missions. Another greedy heuristic might be to try both maximising strength and maximising intellect and choose the maximum number of missions from the two possible options. This also does not work because the optimal solution might not be on a extreme point of strength or intellect. Since the decisions we make need to be flexible enough to change between increasing strength and/or then increasing intellect or vice-versa, it's rather clear that no amount of heuristics will pass all the (corner) test cases.

The next obvious approach to consider is brute force. We can keep track of which missions we have accomplished and how much strength and intellect we have achieved so far, then for all possible mission transitions recurse along the mission and adjust the strength and intellect. In effect, this almost works if we memoise the solution. The problem is keeping track of the missions we have completed - the naive approach is using a bitmask which will definitely exceed the memory constraint (2^50 at worst case).

The trickest part of the problem is state compressing our DP algorithm. If we can somehow reduce the memory footprint whilst also keeping the correctness intact, then a viable solution is generated. Let's take out our bitmask and try to find a way to implicitly represent that information. A common reduction in this situation is to somehow find the relationship between our current DP state parameters (in this case, strength and intellect) and the DP state parameter(s) we want to get rid of (in this case, how we can implicitly represent the missions completed so far). In fact we can see that they have a direct relationship! If we are given a particular strength and intellect we can already uniquely determine which missions can be completed, as we take all viable missions as all the points are strictly positive (always something to gain, nothing to lose).

Now our DP state becomes simply:

D(S, I) = the maximum number of missions if we can get to strength S and intellect I.
D(S, I) = 0 if the state is unreachable (not enough points).

We can proceed to pre-compute the maximum number of missions for each state as there are only 1000 * 1000 = 1 million such states. We can also pre-compute whether or not a state is possible or not. A state is impossible if we don't have enough points to cover the increase from strength 1 to S, and intellect 1 to I. Of course, this doesn't fully dictate the feasibility of a state but it's a good start. To compute the number of excess (unused) points we simply sum up all the points gained from completing the missions and subtracting S-1 and I-1 from it (as we expended S-1 strength points and I-1 intellect points to get to the current state). This calculation is done as follows:

pair<int,int> preComp[1011][1011];
...
   for (int i = 0; i <= 1000; i++) {
      for (int j = 0; j <= 1000; j++) {
         int pointsGained = 0, missions = 0;
         for (int k = 0; k < strength.size(); k++) {
            if (strength[k] <= i || intellect[k] <= j) {
               pointsGained += points[k];
               missions++;
            }
         }
         int extraPoints = pointsGained - i - j + 2;
         // extraPoints < 0 --> inaccessible state set to 0 missions
         // the pair is (excess points, number of missions completed)
         preComp[i][j] = make_pair(extraPoints, extraPoints < 0 ? 0 : missions);
      }
   }
...

Next, we need to determine feasibility from our original DP state (1,1) - which is the strength and intellect we start with. We recursively call the next DP state if we have unused points by either deciding to upgrade our strength by 1 or our intellect by 1. Note the number of unique states is only 1 million so this should run in time. Combining this with a caching mechanism (i.e. memoisation) we come up with our final solution:

class StrengthOrIntellect {
public:
   int numberOfMissions(vector <int>, vector <int>, vector <int>);
};

pair<int,int> preComp[1011][1011];
int memo[1011][1011];

int func(int strength, int intellect) {
   // seen it before (caching mechanism)
   if (memo[strength][intellect] != -1) return memo[strength][intellect];
   memo[strength][intellect] = preComp[strength][intellect].second;
   int extra = preComp[strength][intellect].first;
   // if there are unused points, recurse on them otherwise the maximum
   // is simply just the number of missions we have obtained so far
   return memo[strength][intellect] >?= extra <= 0 ? memo[strength]
      [intellect] : max(func(strength+1, intellect), func(strength, intellect+1));
}

int StrengthOrIntellect::numberOfMissions(vector <int> strength, vector <int> 
      intellect, vector <int> points) {
   int res = 0;
   for (int i = 0; i <= 1000; i++) {
      for (int j = 0; j <= 1000; j++) {
         int pointsGained = 0, missions = 0;
         for (int k = 0; k < strength.size(); k++) {
            if (strength[k] <= i || intellect[k] <= j) {
               pointsGained += points[k];
               missions++;
            }
         }
         int extraPoints = pointsGained - i - j + 2;
         // extraPoints < 0 --> inaccessible state set to 0 missions
         preComp[i][j] = make_pair(extraPoints, extraPoints < 0 ? 0 : missions);
      }
   }
   memset(memo,-1,sizeof(memo));
   return res = func(1, 1);
}