Topcoder SRM 554, D2, 500-point problem

James S. Plank
CS302
September, 2012


Problem description: http://community.topcoder.com/stat?c=problem_statement&pm=12162&rd=15176.

The driver for this is in Brick-Main.cpp, which includes TheBrickTowerMediumDivTwo.cpp and compiles the example cases.

When I first read the problem, I thought, "This is brain-dead -- just sort the towers." But then I saw example #3, and it's clear that sorting is not the solution. Or the whole solution. At that point, I looked at the constraints and saw the heights will have a maximum size of seven. If I simply generate all possible orderings of the towers, then I can figure out which one has the minimum distance and is lexicographically smallest. Will that be fast enough?

Suppose heights has n elements. Generating all possible orderings is equivalent to generating all permutations of towers. It's the same as permuting the numbers from 0 through n-1. How many permutations are there?

n!

When n is 7, n! is 5040, which is a tiny number computationally. Our permutation approach should be easily fast enough. So, here is the strategy for solving the problem:


I have additional lecture notes below from 2012, where I solved it using recursion rather than next_permutation(). Even if you solve it with next_permutation(), I recommend reading through these lecture notes.


We'll generate the permutations in the exact same way that we did in class. Let's do that first, and we'll solve the problem later.

That code is in Brick-1.cpp, and it is pretty much identical to the lecture notes on permutations:

#include <string>
#include <vector>
#include <list>
#include <algorithm>
#include <map>
#include <set>
#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;

class TheBrickTowerMediumDivTwo {
  public:
    vector <int> find(vector <int> heights);
    vector <int> H;
    vector <int> I;
    void Permute(int index);
};

void TheBrickTowerMediumDivTwo::Permute(int index) 
{
  int i, tmp;

  if (index == I.size()) {
    for (i = 0; i < index; i++) cout << I[i] << " ";
    cout << endl;
    return;
  }
  
  /* This is the standard recursive permutation -- swap each element in I 
     with the one in index, and call recursively on index+1 */

  for (i = index; i < I.size(); i++) {
    tmp = I[i];
    I[i] = I[index];
    I[index] = tmp;
    Permute(index+1);
    tmp = I[i];
    I[i] = I[index];
    I[index] = tmp;
  }
}

vector <int> TheBrickTowerMediumDivTwo::find(vector <int> heights)
{
  vector <int> rv;
  int i;

  H = heights;
  for (i = 0; i < H.size(); i++) I.push_back(i);
  Permute(0);
  
  return rv;
}

We run it by copying it to TheBrickTowerMediumDivTwo.cpp and compiling Brick-Main.cpp. An examination of the first two examples convinces me of correctness:

UNIX> cp Brick-1.cpp TheBrickTowerMediumDivTwo.cpp
UNIX> g++ Brick-Main.cpp
UNIX> a.out 0
0 1 2 
0 2 1 
1 0 2 
1 2 0 
2 1 0 
2 0 1 
UNIX> a.out 1 | head
0 1 2 3 4 5 6 
0 1 2 3 4 6 5 
0 1 2 3 5 4 6 
0 1 2 3 5 6 4 
0 1 2 3 6 5 4 
0 1 2 3 6 4 5 
0 1 2 4 3 5 6 
0 1 2 4 3 6 5 
0 1 2 4 5 3 6 
0 1 2 4 5 6 3 
UNIX> a.out 1 | tail
6 0 1 5 3 4 2 
6 0 1 5 3 2 4 
6 0 1 5 2 3 4 
6 0 1 5 2 4 3 
6 0 1 2 4 5 3 
6 0 1 2 4 3 5 
6 0 1 2 5 4 3 
6 0 1 2 5 3 4 
6 0 1 2 3 5 4 
6 0 1 2 3 4 5 
UNIX> a.out 1 | wc
    5040   35280   75600
UNIX>
Now, instead of printing out the indices, let's calculate their distance, and store it if it is minimal. That's in Brick-2.cpp:

class TheBrickTowerMediumDivTwo {
  public:
    vector <int> find(vector <int> heights);
    vector <int> H;
    vector <int> I;
    void Permute(int index);
    int min;
    vector <int> rv;
};

#define MAX(a, b) (((a) > (b)) ? (a) : (b))

void TheBrickTowerMediumDivTwo::Permute(int index) 
{
  int i, tmp, d;

  if (index == I.size()) {
    d = 0;
    for (i = 1; i < index; i++) d += MAX(H[I[i]], H[I[i-1]]);
    if (d < min) {
      min = d;
      rv = I;    /* This throws out the old rv and makes a copy of I. */
    }
    return;
  }
  
  /* This is the standard recursive permutation -- swap each element in I 
     with the one in index, and call recursively on index+1 */

  for (i = index; i < I.size(); i++) {
    tmp = I[i];
    I[i] = I[index];
    I[index] = tmp;
    Permute(index+1);
    tmp = I[i];
    I[i] = I[index];
    I[index] = tmp;
  }
}

vector <int> TheBrickTowerMediumDivTwo::find(vector <int> heights)
{
  int i;

  H = heights;
  min = 48*H.size();
  for (i = 0; i < H.size(); i++) I.push_back(i);

  Permute(0);

  return rv;
}

A few comments about this program. First, the definition of MAX() uses the C preprocessor. Whenever the program does MAX(a, b), the preprocessor does a text substitution of (((a) > (b)) ? (a) : (b)). Why do that instead of just writing a procedure? Well, it's faster. It won't make a difference here, but there are times where you'd like to put in something that looks like a procedure, but really stands for a block of code. Statements like this allow you to do it. When done correctly, they make your program more readable rather than less. Don't go overboard with them, but for things like max and min, they can be convenient.

As an aside, statements like this can backfire. Suppose you do MAX(f(i), f(j)), where f(i) and f(j) are complex and time-consuming procedure calls. Then your macro has backfired, because you will be calling one of f(i) or f(j) twice. Be forewarned.

Second, I sentinelize min so that it has a larger value than any ordering of the towers. That comes from the problem constraints. When I run it, it works for the first two examples, but not for the third:

UNIX> cp Brick-2.cpp TheBrickTowerMediumDivTwo.cpp
UNIX> g++ Brick-Main.cpp 
UNIX> a.out 0
0
2
1
UNIX> a.out 1
0
1
2
3
4
5
6
UNIX> a.out 2
0
3
2
1
UNIX> 
Our solution for example 2 gets an ordering with the minimum distance of eight, but the indices are not lexicographically smallest. We'll solve that by writing another method, Less_than(), which returns whether I is lexicographically less than rv.

This solution is in Brick-3.cpp, and I only include the relevant parts that have changed:

class TheBrickTowerMediumDivTwo {
  public:
    vector <int> find(vector <int> heights);
    vector <int> H;
    vector <int> I;
    void Permute(int index);
    int Less_Than();
    int min;
    vector <int> rv;
};

#define MAX(a, b) (((a) > (b)) ? (a) : (b))

int TheBrickTowerMediumDivTwo::Less_Than()
{
  int i;

  for (i = 0; i < I.size(); i++) {
    if (I[i] < rv[i]) return 1;
    if (rv[i] < I[i]) return 0;
  }
  return 0;  /* This will actually never happen */
}

void TheBrickTowerMediumDivTwo::Permute(int index) 
{
  int i, tmp, d;

  if (index == I.size()) {
    d = 0;
    for (i = 1; i < index; i++) d += MAX(H[I[i]], H[I[i-1]]);
    if (d < min || (d == min && Less_Than())) {
      min = d;
      rv = I;    /* This throws out the old rv and makes a copy of I. */
    }
    return;
  }
  
 ...

Should you be worried that when you call Less_Than() that rv is empty? Probably a little. However, that won't happen, because the first time that you test a set of indices, its value of d will be less than min, and Less_Than() will not be called, as C evaluates its booleans from left-to-right, and stops when it knows that the expression is true or false.

UNIX> cp Brick-3.cpp TheBrickTowerMediumDivTwo.cpp
UNIX> g++ Brick-Main.cpp
UNIX> a.out 0
0
2
1
UNIX> a.out 1
0
1
2
3
4
5
6
UNIX> a.out 2
0
3
1
2
UNIX> a.out 3
0
6
3
1
2
4
5
UNIX> 
Nice -- time to submit!

Lessons from this problem