"x1 y1 x2 y2" |
class ThreeTeleports { public: int shortestDistance(int xMe, int yMe, // "Me" is (xMe,yMe) int xHome, int yHome, // "Home" is (xHome,yHome) vector <string> teleports); // Each teleport is "x1 y1 x2 y2" }; |
# Me Home Teleports Answer - ----- --------------------- -------------------------------------------------------------------------------------- ------ 0 3 3 4 5 {"1000 1001 1000 1002", "1000 1003 1000 1004", "1000 1005 1000 1006"} 3 1 0 0 20 20 {"1 1 18 20", "1000 1003 1000 1004", "1000 1005 1000 1006"} 14 2 0 0 20 20 {"1000 1003 1000 1004", "18 20 1 1", "1000 1005 1000 1006"} 14 3 10 10 10000 20000 {"1000 1003 1000 1004", "3 3 10004 20002", "1000 1005 1000 1006"} 30 4 3 7 10000 30000 {"3 10 5200 4900", "12212 8699 9999 30011", "12200 8701 5203 4845"} 117 5 0 0 1000000000 1000000000 {"0 1 0 999999999", "1 1000000000 999999999 0", "1000000000 1 1000000000 999999999"} 36Here's a picture of example 0. The shortest path goes directly from "Me" to "Home".
Here's a picture of example 1. The shortest path is shaded in yellow.
We're going to see this problem again later this semester. However, its constraints are so small that we can solve it with enumeration. There are only eight places in the frog's universe that we care about:
We're going to solve this problem by enumerating the paths. There are a few details that we're going to have to take care of, though.
#include <string> #include <vector> #include <algorithm> #include <iostream> #include <cstdio> #include <cstdlib> using namespace std; class ThreeTeleports { public: int shortestDistance(int xMe, int yMe, int xHome, int yHome, vector <string> teleports); vector <int> Order; void Permute(int index); }; int ThreeTeleports::shortestDistance(int xMe, int yMe, int xHome, int yHome, vector <string> teleports) { vector <long long> X, Y; int i, x1, x2, y1, y2; /* Create the vectors X and Y, which hold the x and y coordinates of the eight locations that we care about. */ X.push_back(xMe); Y.push_back(yMe); for (i = 0; i < teleports.size(); i++) { sscanf(teleports[i].c_str(), "%d %d %d %d", &x1, &y1, &x2, &y2); X.push_back(x1); Y.push_back(y1); X.push_back(x2); Y.push_back(y2); } X.push_back(xHome); Y.push_back(yHome); /* We use %lld to print out long long's using printf(). */ for (i = 0; i < X.size(); i++) printf("[%lld,%lld]\n", X[i], Y[i]); return 0; } |
I'm storing X and Y as long long vectors. The reason is that the constraints are pretty big: 1,000,000,000. While that number fits easily into an integer, three times that number does not, and since our paths may go from small x/y values to high ones and back, we may have paths that are over 3,000,000,000. So it's smart to use long long's.
We test it on examples 0 and 1, and everything looks good:
UNIX> cp Teleport-1.cpp ThreeTeleports.cpp UNIX> g++ Teleport-Main.cpp UNIX> a.out 0 [3,3] [1000,1001] [1000,1002] [1000,1003] [1000,1004] [1000,1005] [1000,1006] [4,5] 0 UNIX> a.out 1 [0,0] [1,1] [18,20] [1000,1003] [1000,1004] [1000,1005] [1000,1006] [20,20] 0 UNIX>
... vector < vector <long long> >AM; ... /* Create the adjacency matrix. */ AM.resize(X.size()); for (i = 0; i < X.size(); i++) { for (j = 0; j < X.size(); j++) { xd = X[j]-X[i]; if (xd < 0) xd = -xd; yd = Y[j]-Y[i]; if (yd < 0) yd = -yd; AM[i].push_back(xd+yd); } } /* Print the adjacency matrix. */ for (i = 0; i < AM.size(); i++) { for (j = 0; j < AM[i].size(); j++) { printf("%6lld", AM[i][j]); } printf("\n"); } return 0; } |
We run this on examples 0 and 1, verifying that it is correct:
UNIX> cp Teleport-2.cpp ThreeTeleports.cpp UNIX> g++ Teleport-Main.cpp UNIX> a.out 0 0 1995 1996 1997 1998 1999 2000 3 1995 0 1 2 3 4 5 1992 1996 1 0 1 2 3 4 1993 1997 2 1 0 1 2 3 1994 1998 3 2 1 0 1 2 1995 1999 4 3 2 1 0 1 1996 2000 5 4 3 2 1 0 1997 3 1992 1993 1994 1995 1996 1997 0 0 UNIX> a.out 1 0 2 38 2003 2004 2005 2006 40 2 0 36 2001 2002 2003 2004 38 38 36 0 1965 1966 1967 1968 2 2003 2001 1965 0 1 2 3 1963 2004 2002 1966 1 0 1 2 1964 2005 2003 1967 2 1 0 1 1965 2006 2004 1968 3 2 1 0 1966 40 38 2 1963 1964 1965 1966 0 0 UNIX>
... /* Add the teleports to the adjacency matrix. */ AM[1][2] = 10; AM[2][1] = 10; AM[3][4] = 10; AM[4][3] = 10; AM[5][6] = 10; AM[6][5] = 10; ... |
I've highlighed the teleports in the output below:
UNIX> cp Teleport-3.cpp ThreeTeleports.cpp UNIX> g++ Teleport-Main.cpp UNIX> a.out 1 0 2 38 2003 2004 2005 2006 40 2 0 10 2001 2002 2003 2004 38 38 10 0 1965 1966 1967 1968 2 2003 2001 1965 0 10 2 3 1963 2004 2002 1966 10 0 1 2 1964 2005 2003 1967 2 1 0 10 1965 2006 2004 1968 3 2 10 0 1966 40 38 2 1963 1964 1965 1966 0 0 UNIX>
Our first piece of laziness is the following: We're going to generate more paths than we need, by generating all permutations of the numbers from 1 to 7. We'll put zero in the front the permutation, and then treat that as a path. For example, the permutation 1,2,3,4,5,6,7 is the path 0-1-2-3-4-5-6-7, that I explained above.
This approach generates more paths than we need for two reasons:
Our second piece of laziness is using next_permutation() from C++'s algorithm library. This generates the "next" lexicographic permutation of a vector, and we don't need to do recursion.
The code in Teleport-4.cpp does this, and prints out the permutations. What I do here is put all of the numbers from 0 through 7 into a vector path, and keep calling next_permutation() until path[0] changes from 0 to 1. Here's the new code:
... /* Generate and print the permutations of 1-7. We do that with a vector containing the numbers 0 through 7, and keep calling next_permutation() until path[0] changes from 0 to 1. */ for (i = 0; i < 8; i++) path.push_back(i); while (path[0] == 0) { for (i = 0; i < path.size(); i++) printf(" %d", path[i]); printf("\n"); next_permutation(path.begin(), path.end()); } return 0; } |
UNIX> cp Teleport-4.cpp ThreeTeleports.cpp UNIX> g++ Teleport-Main.cpp UNIX> a.out 0 | head -n 10 0 1 2 3 4 5 6 7 0 1 2 3 4 5 7 6 0 1 2 3 4 6 5 7 0 1 2 3 4 6 7 5 0 1 2 3 4 7 5 6 0 1 2 3 4 7 6 5 0 1 2 3 5 4 6 7 0 1 2 3 5 4 7 6 0 1 2 3 5 6 4 7 0 1 2 3 5 6 7 4 UNIX> a.out 0 | tail -n 10 0 7 6 5 3 2 4 1 0 7 6 5 3 4 1 2 0 7 6 5 3 4 2 1 0 7 6 5 4 1 2 3 0 7 6 5 4 1 3 2 0 7 6 5 4 2 1 3 0 7 6 5 4 2 3 1 0 7 6 5 4 3 1 2 0 7 6 5 4 3 2 1 0 UNIX> a.out 0 | wc 5041 40321 85682 UNIX>There are 5041 lines, because the last line prints the return value of the procedure.
Here's the new code (in Teleport-5.cpp):
... minimum = 0x1000000000LL; /* This number is 2^36. */ for (i = 0; i < 8; i++) path.push_back(i); while (path[0] == 0) { /* Here is where we calculate the path length. */ d = 0; for (i = 0; path[i] != 7; i++) { d += AM[path[i]][path[i+1]]; } if (d < minimum) minimum = d; next_permutation(path.begin(), path.end()); } return minimum; } |
This solves the problem fast enough for topcoder, so if all we cared about was solving the topcoder problem, we'd be done.
But.....
What I'm going to do is enumerate teleport orderings. I'm going to label them by their lowest indices in path: 1, 3 and 5, and I need to enumerate all possible orderings of zero through three teleports. Let's start with that code first. I'm going to write a recursive proecedure enumerate_teleports, which will enumerate all strings that contain at most one "1", one "3" and one "5". This is pretty much identical to the recursive permutation enumeration from the Enumeration lecture notes, except at the beginning of the recursive procedure, we store the first index characters of the working string into the vector all_orders. At the end, all_orders will contain the strings we want.
The code is in Teleport-6.cpp. Here's the recursive procedure:
/* Enumerate all orderings of teleports. We'll do this by having "work" start as "135", and then we recursively enumerate permutations, storing each prefix when we enter the recursive procedure. */ void enumerate_teleports(int index, string &work, vector <string> &all_orders) { char tmp; size_t i; all_orders.push_back(work.substr(0, index)); // This stores the prefix - first index characters for (i = index; i < work.size(); i++) { tmp = work[i]; work[i] = work[index]; work[index] = tmp; enumerate_teleports(index+1, work, all_orders); tmp = work[i]; work[i] = work[index]; work[index] = tmp; } } |
And here's the code that prints out the orderings:
/* Do the enumeration of teleport orderings and print it out. */ work = "135"; enumerate_teleports(0, work, all_orders); for (i = 0; i < all_orders.size(); i++) cout << all_orders[i] << endl; exit(0); |
We run it, and you'll see it prints all of the teleport orderings (including the empty string):
UNIX> cp Teleport-6.cpp ThreeTeleports.cpp UNIX> g++ Teleport-Main.cpp UNIX> ./a.out 0 1 13 135 15 153 3 31 315 35 351 5 53 531 51 513 UNIX>
1: 0 -> 1 -> 2 -> 3 -> 4 -> 7 2: 0 -> 2 -> 1 -> 3 -> 4 -> 7 3: 0 -> 1 -> 2 -> 4 -> 3 -> 7 4: 0 -> 2 -> 1 -> 4 -> 3 -> 7We can do this enumeration with a power set enumeration -- if there are n teleports, we enumerate all possible strings of length n composed of the characters '<' and '>'. Each teleport corresponds to a character in the string. If the character is '<', then we travel the teleport in one direction, and if it is '>', then we travel it in the other direction. The code to generate these strings is in Teleport-7.cpp:
/* For each ordering of teleports (t), do a power set enumeration to determine the order through the teleports (o). Print them out. */ for (i = 0; i < all_orders.size(); i++) { t = all_orders[i]; for (j = 0; j < (1 << t.size()); j++) { o = ""; for (k = 0; k < t.size(); k++) { if (j & (1 << k)) o.push_back('<'); else o.push_back('>'); } printf("%3s %3s\n", t.c_str(), o.c_str()); } } } |
Since there are a lot of strings to print (79), I just show the first 15, but you can see how the power set enumeration works:
UNIX> cp Teleport-7.cpp ThreeTeleports.cpp UNIX> g++ Teleport-Main.cpp UNIX> a.out 0 | head -n 15 1 > 1 < 13 >> 13 <> 13 >< 13 << 135 >>> 135 <>> 135 ><> 135 <<> 135 >>< 135 <>< 135 ><< 135 <<< UNIX>
/* Calculate the path lengths from the two strings: t is the ordering of the teleports, composed of the characters '1', '3' and '5' o is the ordering through the teleports, composed of the characters '<' and '>' AM is the adjacency matrix */ long long path_length(const string &t, const string &o, const vector < vector <long long > > &AM) { vector <int> path; int i; long long length; /* Create the path vector */ path.push_back(0); for (i = 0; i < t.size(); i++) { if (o[i] == '<') { path.push_back(t[i]-'0'); path.push_back(t[i]-'0'+1); } else { path.push_back(t[i]-'0'+1); path.push_back(t[i]-'0'); } } path.push_back(7); /* Use that to calculate the path length */ length = 0; for (i = 1; i < path.size(); i++) { length += AM[path[i-1]][path[i]]; } return length; } |
And here's the code where we do the enumeration and calculate the minimum:
/* For each ordering of teleports (t), do a power set enumeration to determine the order through the teleports (o). Use path_lengh() to calculate the path lengths and store the minimum. */ minimum = path_length("", "", AM); /* Handle this case, because it isn't handled in the enumeration below. */ for (i = 0; i < all_orders.size(); i++) { t = all_orders[i]; for (j = 0; j < (1 << t.size()); j++) { o = ""; for (k = 0; k < t.size(); k++) { if (j & (1 << k)) o.push_back('<'); else o.push_back('>'); } d = path_length(t, o, AM); if (d < minimum) minimum = d; } } return minimum; } |
We're done! And instead of enumerating 5040 times, we're only calling path_length() 79 times!
UNIX> cp Teleport-8.cpp ThreeTeleports.cpp ; g++ Teleport-Main.cpp UNIX> for i in 0 1 2 3 4 5 ; do ./a.out $i ; done 3 14 14 30 117 36 UNIX>