User:Jeff/ACM/The Great Wall Game

Back to ACM page

The Great Wall Game
Hua and Shen have invented a simple solitaire board game that they call "The Great Wall Game." The game is played with n stones on an n x n grid. The stones are placed at random in the squares of the grid, at most one stone per square. In a single move, any single stone can move into an unoccupied location one unit horizontally or vertically in the grid. The goal of the puzzle is to create a "wall," i.e., to line up all n stones in a straight line either horizontally, vertically, or diagonally using the fewest number of moves. An example for the case n = 5 is shown in Figure 1(a). In Figure 1(b) it is shown that with six moves we can line all the stones up diagonally. No smaller number of moves suffices to create a line of five stones. (However, there are other solutions using six moves, e.g., we can line up all five stones in the third column using six moves.)

There is just one problem -- Hua and Shen have no idea what the minimum number of moves is for any given starting board. They would like you to write a program that can take any starting configuration and determine the fewest number of moves needed to create a wall.

Input
The input consists of multiple cases. Each case begins with a line containing an integer n, 1 &le; n &le; 15. The next line contains the row and column numbers of the first stone, followed by the row and column numbers of the second stone, and so on. Rows and columns are numbered as in the above diagram. The input data for the last case will be followed by a line containing a single zero.

Output
For each input case, display the case number (1, 2, ...) followed by the minimum number of moves needed to line up the n stones into a straight-line wall. Follow the format shown in the sample output.

Analysis
The input restrictions state that there can be at most 15 stones on a 15 x 15 board, which means a maximum of 32 possible configurations for the wall (15 rows, 15 columns, and 2 diagonals). This is a small enough number to attempt to reach each ending configuration, and return the smallest number of optimal moves found.

To solve this problem efficiently, we must make the following assumption: the shortest number of moves needed to get a peg from square a to square b is always equal to the Manhattan distance between points a and b, regardless of the positions of other stones on the board. (Manhattan distance is calculated by adding the absolute values of the differences between the x- and y-coordinates.) This assumption is true because the stones are all uniform; if one stone is in the way of another stone's shortest path to its destination, the second stone can move to that destination instead while the first stone takes the second one's place.

Therefore, the problem can be reduced to matching each stone from the input to its destination position in the final wall. Since the order in which the stones move doesn't matter, there are n! possible matchings. By using memoization, we can reduce the runtime to O(2n) calculations. This gives us a total runtime of O(n 2n), which is only on the order of one million in the worst case.

Using memoization for this problem
Given an array stones[0..n-1] and an array wall[0..n-1], we need to find the best matching between the elements in the arrays. We can write this function recursively by assuming that elements 0..x-1 in stones have already been matched. The function finds the best placement of stones[x] in the positions still available in wall through brute force, and recurses to match the remainder of the stones.

int bestMatch(vector &mem, int n, vector &stones,    vector &wall, int x, int wallMask) { if (mem[wallMask] != -1) return mem[wallMask]; if (x == n) return mem[wallMask] = 0; int ans(INF); for (int i(0); i < n; ++i) { if (wallMask & (1< p2i; int solve(int, vector &); int bestMatch(int, vector &, vector &); int bestMatch(vector &, int, vector &, vector &, int, int); int dist(p2i&, p2i&); int main { for (int i(1); ; ++i) { int n;    cin >> n;     if (n == 0) return 0; vector stones(n); for (int j(0); j < n; ++j) cin >> stones[j].X >> stones[j].Y;    int ans(solve(n, stones)); if (i > 1) cout << endl; cout << "Board "<<i<<": "<<ans<<" moves required." << endl; } } int solve(int n, vector &stones) { int ans(INF); vector wall(n); for (int i(1); i <= n; ++i) { for (int j(0); j < n; ++j) wall[j] = p2i(i, j+1); ans <?= bestMatch(n, stones, wall); for (int j(0); j < n; ++j) wall[j] = p2i(j+1, i); ans <?= bestMatch(n, stones, wall); }  for (int i(0); i < n; ++i) wall[i] = p2i(i+1, i+1); ans <?= bestMatch(n, stones, wall); for (int i(0); i < n; ++i) wall[i] = p2i(i+1, n-i); ans <?= bestMatch(n, stones, wall); return ans; } int bestMatch(int n, vector &stones, vector &wall) { vector mem((1<?(b.X-a.X)) + ((a.Y-b.Y)>?(b.Y-a.Y)); }
 * 1) include
 * 2) include
 * 1) define X first
 * 2) define Y second
 * 3) define INF (1<<30)