CS302 Lecture Notes - Dynamic Programming
Example program #7: Counting Apples


This is Dumitru's Intermediate problem, and it is really no harder than the subsequence problem above. The problem is as follows:

You can view this as a graph problem -- the "table" is a graph, and there are only edges going down and right. Your job is to find the maximum weight path through the graph, where the weight of a path is the sum of all the node weights on the path.

Unlike finding the minimum weight path, finding the maximum weight path is an "NP-Complete" problem -- its solution is exponential (or at least, that's the best we can do with current knowledge). However, if we simply view it as a dynamic program, we can solve it without worrying about its running time complexity. Spotting the recursion here is pretty easy. The maximum weight path to the cell at row r and column c is equal to the number apples in the cell, plus the maximum of the maximum weight path to the cell to the left, and the cell above the given cell. If we then find the maximum weight path to the lower right-hand cell, we will have found the maximum weight path through the graph.

The code is in src/apples1.cpp

#include <iostream> 
#include <vector>
#include <string>
using namespace std;

typedef vector <int> IArray;

class Apple {
  public:
    int rows;
    int cols;
    vector <IArray> apples;
    int find_max(int r1, int c1);
};

int Apple::find_max(int r, int c)
{
  int a;
  int r1, r2;

  a = apples[r][c];
  if (r == 0 && c == 0) return a;
  if (r == 0) return a + find_max(r, c-1);
  if (c == 0) return a + find_max(r-1, c);
  r1 = find_max(r, c-1);
  r2 = find_max(r-1, c);
  return (r1 > r2) ? a+r1 : a+r2;
}

main(int argc, char **argv)
{
  int r, c;
  Apple a;

  if (argc != 3) {
    cerr << "usage: apples1 rows cols -- apples on standard input\n";
    exit(1);
  }
  
  a.rows = atoi(argv[1]);
  a.cols = atoi(argv[2]);
  a.apples.resize(a.rows);
  for (r = 0; r < a.rows; r++) a.apples[r].resize(a.cols);
  
  for (r = 0; r < a.rows; r++) {
    for (c = 0; c < a.cols; c++) {
      cin >> a.apples[r][c];
      if (cin.fail()) {
        cerr << "Not enough apples\n";
        exit(1);
      }
    }
  }
  cout << a.find_max(a.rows-1, a.cols-1) << endl;
}

We can see it working on some small examples:

UNIX> cat a1.txt
5 10
6 4
UNIX> apples1 2 2 < a1.txt
19
UNIX> cat a2.txt
18 32 88 
03 85 29 
64 89 88 
UNIX> apples1 3 3 < a2.txt
312
UNIX> calc 18+32+85+89+88
312.000000
UNIX> 
Let's try a bigger example:
UNIX> cat a3.txt
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20
UNIX> apples1 10 20 < a3.txt
390
UNIX> 
Is that right? The best path will be to take the top row all the way to the right and then drop down. That path will have a weight of (19*20)/2 + 20*10 = 390. Yes, that is right. Let's try it with two times the number of rows:
UNIX> cat a3.txt a3.txt | apples1 20 20
It hangs, so we must memoize. Again, quite straightforward: (In src/apples2.cpp):


typedef vector <int> IArray;
int Apple::find_max(int r, int c)
{
  int a;
  int r1, r2;
  int retval;

  if (cache[r][c] != -1) return cache[r][c];

  a = apples[r][c];
  if (r == 0 && c == 0) {
    retval = a;
  } else if (r == 0) {
    retval = a + find_max(r, c-1);
  } else if (c == 0) {
    retval = a + find_max(r-1, c);
  } else {
    r1 = find_max(r, c-1);
    r2 = find_max(r-1, c);
    if (r1 > r2) {
      retval =  a+r1;
    } else {
      retval = a+r2;
    }
  }
  cache[r][c] = retval;
  return retval;
}

UNIX> cat a3.txt a3.txt | apples2 20 20
590
UNIX> cat a3.txt a3.txt a3.txt a3.txt a3.txt a3.txt a3.txt a3.txt a3.txt a3.txt | apples2 100 20
2190
Nice. In src/apples3.cpp, we remove the recursion. We do so by starting at the beginning of the cache and filling in to the higher values:

int Apple::find_max()
{
  int r1, r2;
  int retval;
  int r, c;
  
  cache[0][0] = apples[0][0];
  for (r = 1; r < rows; r++) cache[r][0] = apples[r][0] + cache[r-1][0];
  for (c = 1; c < cols; c++) cache[0][c] = apples[0][c] + cache[0][c-1];
  
  for (r = 1; r < rows; r++) {
    for (c = 1; c < cols; c++) {
      r1 = cache[r][c-1];
      r2 = cache[r-1][c];
      if (r1 > r2) {
        cache[r][c] =  apples[r][c]+r1;
      } else {
        cache[r][c] = apples[r][c]+r2;
      }
    }
  }
  return cache[rows-1][cols-1];
}

As with the maximum subsequence problem, we can reduce the cache size to two rows. I won't do it here -- see if you can do it yourself!