Problem:
MathJax.Hub.Config({ tex2jax: { inlineMath: [["$","$"], ["\\(","\\)"]], processEscapes: true }});

15 Puzzle

The goal of the 15 puzzle problem is to complete pieces on $4 \times 4$ cells where one of the cells is empty space.

In this problem, the space is represented by 0 and pieces are represented by integers from 1 to 15 as shown below.

1 2 3 4
6 7 8 0
5 10 11 12
9 13 14 15

You can move a piece toward the empty space at one step. Your goal is to make the pieces the following configuration in the shortest move (fewest steps).

1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 0

Write a program which reads an initial state of the puzzle and prints the fewest steps to solve the puzzle.

Input

The $4 \times 4$ integers denoting the pieces or space are given.

Output

Print the fewest steps in a line.

Constraints

The given puzzle is solvable in at most 45 steps.

Sample Input

1 2 3 4
6 7 8 0
5 10 11 12
9 13 14 15

Sample Output

8

Solution in Ruby (judge verdict: Time Limit Exceeded)

class FifteenPuzzle
  N = 4
  N2 = N * N
  DX = [ 0, 1, 0, -1 ]
  DY = [ 1, 0, -1, 0 ]
  DIR = [ 'u', 'r', 'd', 'l' ]
  MDTable = Array.new(N2).map { Array.new(N2) }

  class PriorityQueue
    attr_accessor :tree

    def initialize
      @tree = [nil]
      @heapsize = 0
    end

    def push(state)
      @heapsize += 1
      @tree[@heapsize] = Float::INFINITY
      heap_increase_key(@heapsize, state)
    end

    def parent(i)
      i / 2
    end

    def heap_increase_key(i, state)
      raise Exception.new if state.estimate > @tree[i]
      @tree[i] = state
      while parent(i) > 0 && i > 0 && @tree[parent(i)].estimate > @tree[i].estimate
        @tree[parent(i)], @tree[i] = @tree[i], @tree[parent(i)]
        i = parent(i)
      end
    end

    def pop
      raise Exception.new if @heapsize < 1
      min = @tree[1]
      @tree[1] = @tree[@heapsize]
      @tree.delete_at(@heapsize)
      @heapsize -= 1
      min_heapify(1)
      return min
    end

    def min_heapify(i)
      l = i * 2
      r = i * 2 + 1
      if l <= @heapsize && @tree[l].estimate < @tree[i].estimate
        minimum = l
      else
        minimum = i
      end
      if r <= @heapsize && @tree[r].estimate < @tree[minimum].estimate
        minimum = r
      end

      if minimum != i
        @tree[i], @tree[minimum] = @tree[minimum], @tree[i]
        min_heapify(minimum)
      end
    end

    def empty?
      return true if @heapsize == 0
      return false
    end
  end

  class Puzzle
    attr_accessor :cost, :space, :f, :md

    def initialize
      @f = []
    end

    # get current md
    def calc_all_md
      sum = 0
      (0...N2).each do |i|
        sum += MDTable[i][@f[i] - 1] if @f[i] != N2
      end
      @md = sum
    end

    def initialize_copy(obj)
      @f = obj.f.dup
      @space = obj.space
      @cost = obj.cost
      @md = obj.md
    end
  end

  class State
    attr_accessor :estimate, :puzzle

    def initialize
      @puzzle = Array.new(N2)
    end

  end
  def initialize
    # init MDtable
    (0...N2).each do |i|
      (0...N2).each do |j|
        MDTable[i][j] = get_manhattan_distance(i % N, i / N, j % N, j / N)
      end
    end

    @puzzle = Puzzle.new
    4.times do
      @puzzle.f << gets.split.map(&:to_i)
      @puzzle.f.flatten!
    end

    (0...N2).each do |i|
      if @puzzle.f[i] == 0
        @puzzle.f[i] = N2
        @puzzle.space = i
      end
    end
  end

  def astar
    q = PriorityQueue.new
    @puzzle.calc_all_md
    @puzzle.cost = 0
    ex = {}
    initial_state = State.new
    initial_state.puzzle = @puzzle
    initial_state.estimate = @puzzle.md
    q.push initial_state

    while !q.empty?
      st = q.pop
      u = st.puzzle

      return u.cost if u.md == 0
      ex[u.f] = true

      sx = u.space / N
      sy = u.space % N

      (0..3).each do |i|
        tx = sx + DX[i]
        ty = sy + DY[i]
        next if tx < 0 || N <= tx || ty < 0 || N <= ty
        v = u.dup
        v.md -= MDTable[tx * N + ty][v.f[tx * N + ty] - 1]
        v.md += MDTable[sx * N + sy][v.f[tx * N + ty] - 1]
        # swap
        v.f[sx * N + sy], v.f[tx * N + ty] = v.f[tx * N + ty], v.f[sx * N + sy]
        v.space = tx * N + ty
        unless ex[v]
          v.cost += 1
          new_state = State.new
          new_state.puzzle = v
          new_state.estimate = v.cost + v.md
          q.push new_state
        end
      end
    end
    return -1
  end

  def get_manhattan_distance(x1, y1, x2, y2)
    return (x1 - x2).abs + (y1 - y2).abs
  end

end

fp = FifteenPuzzle.new
puts fp.astar