Problem:
Problem G: Time Trial

Some people like finishing computer games in an extremely short time. Terry A. Smith is one of such
and prefers role playing games particularly.

He is now trying to find a shorter play for one of the key events in a role playing game. In this event,
a player is presented a kind of puzzle on a grid map with three rocks and three marked squares. The
objective is to have all the rocks placed on the marked squares by controlling the hero of this game
appropriately.

Figure 1: Example Map

The hero can move to the four adjacent squares, that is, to the north, east, south, and west, unless his move
is blocked by walls or rocks. He can never enter squares occupied by walls. On the other hand, when he
is moving to a square occupied by a rock, he pushes the rock in his moving direction. Nonetheless, he
cannot push the rock if the next square is occupied by a wall or another rock and his move is blocked in
this case. Also, he can only move one rock at a time. It is allowed to have rocks pass through marked
squares.

Terry thinks he can reduce his playing time by finding the optimal way to move the rocks and then playing
the event accordingly. However, it is too hard for him to find the solution of this puzzle by hand. So you
are asked by him to write a program that finds the smallest number of steps for the maps given as the
input. Here, each move from a square to its adjacent square is counted as one step.

Input

The input is a sequence of datasets. Each dataset has the following format:

W H
Row1
...
RowH

W and H are the width and height of the map (4 ≤ W, H ≤ 16). Rowi denotes the i-th row of the map
and consists of W characters. Each character represents a square and is one of the following: ‘#’ (wall), ‘.’ (floor), ‘*’ (rock), ‘_’ (marked square), and ‘@’ (hero). Each map contains exactly three rocks, three
marked squares, and one hero. The outermost squares are always occupied by walls. You may assume
that the number of non-wall squares does not exceed fifty. It is also guaranteed that there is at least one
solution for every map.

The input is terminated by a line with two zeros. This line is not part of any datasets and should not be
processed.

Output

For each dataset, print the smallest number of steps in a line.

Sample Input

7 6
#######
#.._..#
#.*.*.#
#.@.*.#
#_..._#
#######
10 13
##########
####___###
####...###
####...###
#####.####
#.....#..#
#.#*.*.*.#
#...###..#
###.#.#.##
###.#.#.##
###.....##
###..@..##
##########
0 0

Output for the Sample Input

15
118

Solution in JAVA (judge verdict: Accepted)

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.PriorityQueue;
import java.util.Scanner;
import java.util.TreeSet;

public class Main {
	public static final int MAX = 16;
	public static final int F_MAX = 50;
	
	public static int[][] move_dir = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
	
	public static boolean is_ok(int x, int y, int h, int w){
		if(x < 0 || x >= w || y < 0 || y >= h){
			return false;
		}else{
			return true;
		}
	}

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		
		int[] x_map = new int[F_MAX];
		int[] y_map = new int[F_MAX];
		
		int[][] attached = new int[MAX][MAX];
		
		boolean[][] can_enter = new boolean[MAX][MAX];
		boolean[][] goal = new boolean[MAX][MAX];
		
		boolean[][][][] visited = new boolean[F_MAX][F_MAX][F_MAX][F_MAX];

		while (true) {
			final int W = sc.nextInt();
			final int H = sc.nextInt();

			if (W == 0 && H == 0) {
				break;
			}
			
			System.gc();
			
			for(int i = 0; i < F_MAX; i++){
				for(int j = 0; j < F_MAX; j++){
					for(int k = 0; k < F_MAX; k++){
						for(int l = 0; l < F_MAX; l++){
							visited[i][j][k][l] = false;
						}
					}
				}
			}

			int start = -1;
			int s_r1 = -1, s_r2 = -1, s_r3 = -1;
			
			int attach_count = 0;
			int rock_count = 0;
			for (int i = 0; i < H; i++) {
				char[] input = sc.next().toCharArray();

				for (int j = 0; j < W; j++) {
					if (input[j] == '@') {
						start = attach_count;
					} else if (input[j] == '*') {
						if(rock_count == 0){
							s_r1 = attach_count;
						}else if(rock_count == 1){
							s_r2 = attach_count;
						}else if(rock_count == 2){
							s_r3 = attach_count;
						}
						
						rock_count++;
					}
					
					if (input[j] == '#') {
						can_enter[i][j] = false;
						attached[i][j] = -1;
					} else {
						can_enter[i][j] = true;
						x_map[attach_count] = j;
						y_map[attach_count] = i;
						attached[i][j] = attach_count;
						attach_count++;
					}

					if (input[j] == '_') {
						goal[i][j] = true;
					} else {
						goal[i][j] = false;
					}
				}
			}

		
			LinkedList<Integer> pos_queue = new LinkedList<Integer>();
			LinkedList<Integer> r1_queue = new LinkedList<Integer>();
			LinkedList<Integer> r2_queue = new LinkedList<Integer>();
			LinkedList<Integer> r3_queue = new LinkedList<Integer>();
			LinkedList<Integer> time_queue = new LinkedList<Integer>();
			
			pos_queue.add(start);
			r1_queue.add(s_r1);
			r2_queue.add(s_r2);
			r3_queue.add(s_r3);
			time_queue.add(0);

			while (!time_queue.isEmpty()) {
				final int pos = pos_queue.poll();
				final int r1  = r1_queue.poll();
				final int r2  = r2_queue.poll();
				final int r3  = r3_queue.poll();
				final int time = time_queue.poll();
				//System.out.println(bit + " " + time + " " + set.size());
				
				final int pos_x = x_map[pos];
				final int pos_y = y_map[pos];
				final int r1_x =  x_map[r1];
				final int r1_y =  y_map[r1];
				final int r2_x =  x_map[r2];
				final int r2_y =  y_map[r2];
				final int r3_x =  x_map[r3];
				final int r3_y =  y_map[r3];
				
				if(goal[r1_y][r1_x] && goal[r2_y][r2_x] && goal[r3_y][r3_x]){
					System.out.println(time);
					break;
				}
				
				//System.out.println(pos_x + " " + pos_y + " " + r1_x + " " + r1_y + " " + r2_x + " " + r2_y + " " + r3_x + " " + r3_y);
				
				for(int move[] : move_dir){
					final int nx = pos_x + move[0];
					final int ny = pos_y + move[1];
					
					//System.out.println(x + " " + y + " " + nx + " " + ny);
					
					int n_r1_x = r1_x;
					int n_r1_y = r1_y;
					int n_r2_x = r2_x;
					int n_r2_y = r2_y;
					int n_r3_x = r3_x;
					int n_r3_y = r3_y;
					
					if(nx == n_r1_x && ny == n_r1_y){
						n_r1_x += move[0];
						n_r1_y += move[1];
					}
					
					if(nx == n_r2_x && ny == n_r2_y){
						n_r2_x += move[0];
						n_r2_y += move[1];
					}
					
					if(nx == n_r3_x && ny == n_r3_y){
						n_r3_x += move[0];
						n_r3_y += move[1];
					}
					//System.out.println("5");
					
					if(!is_ok(nx, ny, H, W)){
						continue;
					}else if(!is_ok(n_r1_x, n_r1_y, H, W)){
						continue;
					}else if(!is_ok(n_r2_x, n_r2_y, H, W)){
						continue;
					}else if(!is_ok(n_r3_x, n_r3_y, H, W)){
						continue;
					}else if(n_r1_x == n_r2_x && n_r1_y == n_r2_y){
						//System.out.println(1);
						continue;
					}else if(n_r2_x == n_r3_x && n_r2_y == n_r3_y){
						//System.out.println(2);
						continue;
					}else if(n_r1_x == n_r3_x && n_r1_y == n_r3_y){
						//System.out.println(3);
						continue;
					}else if(attached[ny][nx] == -1){
						continue;
					}else if(attached[n_r1_y][n_r1_x] == -1){
						continue;
					}else if(attached[n_r2_y][n_r2_x] == -1){
						continue;
					}else if(attached[n_r3_y][n_r3_x] == -1){
						continue;
					}
					
					//System.out.println("t");
					
					final int n_pos = attached[ny][nx];
					final int n_r1  = attached[n_r1_y][n_r1_x];
					final int n_r2  = attached[n_r2_y][n_r2_x];
					final int n_r3  = attached[n_r3_y][n_r3_x];
					
					if(visited[n_pos][n_r1][n_r2][n_r3]){
						continue;
					}
					
					visited[n_pos][n_r1][n_r2][n_r3] = true;
					pos_queue.add(n_pos);
					r1_queue.add(n_r1);
					r2_queue.add(n_r2);
					r3_queue.add(n_r3);
					time_queue.add(time + 1);
				}
				
			}

		}

		sc.close();
	}

}